deploy: 5277cd16f4e98fb60f64f9252e3d3fc64b43b548

This commit is contained in:
jgsuess 2025-08-04 12:19:16 +00:00
commit 9571b57d1a
29 changed files with 51249 additions and 0 deletions

0
.nojekyll Normal file
View File

855
README.html Normal file
View File

@ -0,0 +1,855 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Begin Jekyll SEO tag v2.8.0 -->
<title>FHIRFLARE IG Toolkit | Helm chart for deploying the fhirflare-ig-toolkit application</title>
<meta name="generator" content="Jekyll v3.9.5" />
<meta property="og:title" content="FHIRFLARE IG Toolkit" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Helm chart for deploying the fhirflare-ig-toolkit application" />
<meta property="og:description" content="Helm chart for deploying the fhirflare-ig-toolkit application" />
<meta property="og:site_name" content="FHIRFLARE IG Toolkit" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="FHIRFLARE IG Toolkit" />
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"WebPage","description":"Helm chart for deploying the fhirflare-ig-toolkit application","headline":"FHIRFLARE IG Toolkit","url":"/README.html"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/css/style.css?v=5277cd16f4e98fb60f64f9252e3d3fc64b43b548">
<script src="/assets/js/scale.fix.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- start custom head snippets, customize with your own _includes/head-custom.html file -->
<!-- Setup Google Analytics -->
<!-- You can set your favicon here -->
<!-- link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" -->
<!-- end custom head snippets -->
</head>
<body>
<div class="wrapper">
<header >
<h1>FHIRFLARE IG Toolkit</h1>
<p>Helm chart for deploying the fhirflare-ig-toolkit application</p>
<p class="view"><a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit">View the Project on GitHub <small></small></a></p>
<ul>
<li><a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/zipball/gh-pages">Download <strong>ZIP File</strong></a></li>
<li><a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/tarball/gh-pages">Download <strong>TAR Ball</strong></a></li>
<li><a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit">View On <strong>GitHub</strong></a></li>
</ul>
</header>
<section>
<h1 id="fhirflare-ig-toolkit">FHIRFLARE IG Toolkit</h1>
<p><img src="/static/FHIRFLARE.png" alt="FHIRFLARE Logo" /></p>
<h2 id="overview">Overview</h2>
<p>The FHIRFLARE IG Toolkit is a Flask-based web application designed to streamline the management, processing, validation, and deployment of FHIR Implementation Guides (IGs) and test data. It offers a user-friendly interface for importing IG packages, extracting metadata, validating FHIR resources or bundles, pushing IGs to FHIR servers, converting FHIR resources to FHIR Shorthand (FSH), uploading complex test data sets with dependency management, and retrieving/splitting FHIR bundles. The toolkit includes live consoles for real-time feedback, making it an essential tool for FHIR developers and implementers.</p>
<p>The application can run in two modes:</p>
<ul>
<li><strong>Standalone:</strong> Includes a Dockerized Flask frontend, SQLite database, and an embedded HAPI FHIR server for local validation and interaction.</li>
<li><strong>Lite:</strong> Includes only the Dockerized Flask frontend and SQLite database, excluding the local HAPI FHIR server. Requires connection to external FHIR servers for certain features.</li>
</ul>
<h2 id="installation-modes-lite-vs-standalone">Installation Modes (Lite vs. Standalone)</h2>
<p>This toolkit offers two primary installation modes to suit different needs:</p>
<ul>
<li><strong>Standalone Version:</strong>
<ul>
<li>Includes the full FHIRFLARE Toolkit application <strong>and</strong> an embedded HAPI FHIR server running locally within the Docker environment.</li>
<li>Allows for local FHIR resource validation using HAPI FHIRs capabilities.</li>
<li>Enables the “Use Local HAPI” option in the FHIR API Explorer and FHIR UI Operations pages, proxying requests to the internal HAPI server (<code class="language-plaintext highlighter-rouge">http://localhost:8080/fhir</code>).</li>
<li>Requires Git and Maven during the initial build process (via the <code class="language-plaintext highlighter-rouge">.bat</code> script or manual steps) to prepare the HAPI FHIR server.</li>
<li>Ideal for users who want a self-contained environment for development and testing or who dont have readily available external FHIR servers.</li>
</ul>
</li>
<li><strong>Lite Version:</strong>
<ul>
<li>Includes the FHIRFLARE Toolkit application <strong>without</strong> the embedded HAPI FHIR server.</li>
<li>Requires users to provide URLs for external FHIR servers when using features like the FHIR API Explorer and FHIR UI Operations pages. The “Use Local HAPI” option will be disabled in the UI.</li>
<li>Resource validation relies solely on local checks against downloaded StructureDefinitions, which may be less comprehensive than HAPI FHIRs validation (e.g., for terminology bindings or complex invariants).</li>
<li><strong>Does not require Git or Maven</strong> for setup if using the <code class="language-plaintext highlighter-rouge">.bat</code> script or running the pre-built Docker image.</li>
<li>Ideal for users who primarily want to use the IG management, processing, and FSH conversion features, or who will always connect to existing external FHIR servers.</li>
</ul>
</li>
</ul>
<h2 id="features">Features</h2>
<ul>
<li><strong>Import IGs:</strong> Download FHIR IG packages and dependencies from a package registry, supporting flexible version formats (e.g., <code class="language-plaintext highlighter-rouge">1.2.3</code>, <code class="language-plaintext highlighter-rouge">1.1.0-preview</code>, <code class="language-plaintext highlighter-rouge">current</code>) and dependency pulling modes (Recursive, Patch Canonical, Tree Shaking).</li>
<li><strong>Enhanced Package Search and Import:</strong>
<ul>
<li>Interactive page (<code class="language-plaintext highlighter-rouge">/search-and-import</code>) to search for FHIR IG packages from configured registries.</li>
<li>Displays package details, version history, dependencies, and dependents.</li>
<li>Utilizes a local database cache (<code class="language-plaintext highlighter-rouge">CachedPackage</code>) for faster subsequent searches.</li>
<li>Background task to refresh the package cache from registries (<code class="language-plaintext highlighter-rouge">/api/refresh-cache-task</code>).</li>
<li>Direct import from search results.</li>
</ul>
</li>
<li><strong>Manage IGs:</strong> View, process, unload, or delete downloaded IGs, with duplicate detection and resolution.</li>
<li><strong>Process IGs:</strong> Extract resource types, profiles, must-support elements, examples, and profile relationships (<code class="language-plaintext highlighter-rouge">structuredefinition-compliesWithProfile</code> and <code class="language-plaintext highlighter-rouge">structuredefinition-imposeProfile</code>).</li>
<li><strong>Validate FHIR Resources/Bundles:</strong> Validate single FHIR resources or bundles against selected IGs, with detailed error and warning reports (alpha feature). <em>Note: Lite version uses local SD checks only.</em></li>
<li><strong>Push IGs:</strong> Upload IG resources (and optionally dependencies) to a target FHIR server. Features include:
<ul>
<li>Real-time console output.</li>
<li>Authentication support (Bearer Token).</li>
<li>Filtering by resource type or specific files to skip.</li>
<li>Semantic comparison to skip uploading identical resources (override with <strong>Force Upload</strong> option).</li>
<li>Correct handling of canonical resources (searching by URL/version before deciding POST/PUT).</li>
<li>Dry run mode for simulation.</li>
<li>Verbose logging option.</li>
</ul>
</li>
<li><strong>Upload Test Data:</strong> Upload complex sets of test data (individual JSON/XML files or ZIP archives) to a target FHIR server. Features include:
<ul>
<li>Robust parsing of JSON and XML (using <code class="language-plaintext highlighter-rouge">fhir.resources</code> library when available).</li>
<li>Automatic dependency analysis based on resource references within the uploaded set.</li>
<li>Topological sorting to ensure resources are uploaded in the correct order.</li>
<li>Cycle detection in dependencies.</li>
<li>Choice of individual resource uploads or a single transaction bundle.</li>
<li><strong>Optional Pre-Upload Validation:</strong> Validate resources against a selected profile package before uploading.</li>
<li><strong>Optional Conditional Uploads (Individual Mode):</strong> Check resource existence (GET) and use conditional <code class="language-plaintext highlighter-rouge">If-Match</code> headers for updates (PUT) or create resources (PUT/POST). Falls back to simple PUT if unchecked.</li>
<li>Configurable error handling (stop on first error or continue).</li>
<li>Authentication support (Bearer Token).</li>
<li>Streaming progress log via the UI.</li>
<li>Handles large numbers of files using a custom form parser.</li>
</ul>
</li>
<li><strong>Profile Relationships:</strong> Display and validate <code class="language-plaintext highlighter-rouge">compliesWithProfile</code> and <code class="language-plaintext highlighter-rouge">imposeProfile</code> extensions in the UI (configurable).</li>
<li><strong>FSH Converter:</strong> Convert FHIR JSON/XML resources to FHIR Shorthand (FSH) using GoFSH, with advanced options (Package context, Output styles, Log levels, FHIR versions, Fishing Trip, Dependencies, Indentation, Meta Profile handling, Alias File, No Alias). Includes a waiting spinner.</li>
<li><strong>Retrieve and Split Bundles:</strong>
<ul>
<li>Retrieve specified resource types as bundles from a FHIR server.</li>
<li>Optionally fetch referenced resources, either individually or as full bundles for each referenced type.</li>
<li>Split uploaded ZIP files containing bundles into individual resource JSON files.</li>
<li>Download retrieved/split resources as a ZIP archive.</li>
<li>Streaming progress log via the UI for retrieval operations.</li>
</ul>
</li>
<li><strong>FHIR Interaction UIs:</strong> Explore FHIR server capabilities and interact with resources using the “FHIR API Explorer” (simple GET/POST/PUT/DELETE) and “FHIR UI Operations” (Swagger-like interface based on CapabilityStatement). <em>Note: Lite version requires custom server URLs.</em></li>
<li><strong>HAPI FHIR Configuration (Standalone Mode):</strong>
<ul>
<li>A dedicated page (<code class="language-plaintext highlighter-rouge">/config-hapi</code>) to view and edit the <code class="language-plaintext highlighter-rouge">application.yaml</code> configuration for the embedded HAPI FHIR server.</li>
<li>Allows modification of HAPI FHIR properties directly from the UI.</li>
<li>Option to restart the HAPI FHIR server (Tomcat) to apply changes.</li>
</ul>
</li>
<li><strong>API Support:</strong> RESTful API endpoints for importing, pushing, retrieving metadata, validating, uploading test data, and retrieving/splitting bundles.</li>
<li><strong>Live Console:</strong> Real-time logs for push, validation, upload test data, FSH conversion, and bundle retrieval operations.</li>
<li><strong>Configurable Behavior:</strong> Control validation modes, display options via <code class="language-plaintext highlighter-rouge">app.config</code>.</li>
<li><strong>Theming:</strong> Supports light and dark modes.</li>
</ul>
<h2 id="technology-stack">Technology Stack</h2>
<ul>
<li>Python 3.12+, Flask 2.3.3, Flask-SQLAlchemy 3.0.5, Flask-WTF 1.2.1</li>
<li>Jinja2, Bootstrap 5.3.3, JavaScript (ES6), Lottie-Web 5.12.2</li>
<li>SQLite</li>
<li>Docker, Docker Compose, Supervisor</li>
<li>Node.js 18+ (for GoFSH/SUSHI), GoFSH, SUSHI</li>
<li>HAPI FHIR (Standalone version only)</li>
<li>Requests 2.31.0, Tarfile, Logging, Werkzeug</li>
<li>fhir.resources (optional, for robust XML parsing)</li>
</ul>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><strong>Docker:</strong> Required for containerized deployment (both versions).</li>
<li><strong>Git &amp; Maven:</strong> Required <strong>only</strong> for building the <strong>Standalone</strong> version from source using the <code class="language-plaintext highlighter-rouge">.bat</code> script or manual steps. Not required for the Lite version build or for running pre-built Docker Hub images.</li>
<li><strong>Windows:</strong> Required if using the <code class="language-plaintext highlighter-rouge">.bat</code> scripts.</li>
</ul>
<h2 id="setup-instructions">Setup Instructions</h2>
<h3 id="running-pre-built-images-general-users">Running Pre-built Images (General Users)</h3>
<p>This is the easiest way to get started without needing Git or Maven. Choose the version you need:</p>
<p><strong>Lite Version (No local HAPI FHIR):</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Pull the latest Lite image</span>
docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest
<span class="c"># Run the Lite version (maps port 5000 for the UI)</span>
<span class="c"># You'll need to create local directories for persistent data first:</span>
<span class="c"># mkdir instance logs static static/uploads instance/hapi-h2-data</span>
docker run <span class="nt">-d</span> <span class="se">\</span>
<span class="nt">-p</span> 5000:5000 <span class="se">\</span>
<span class="nt">-v</span> ./instance:/app/instance <span class="se">\</span>
<span class="nt">-v</span> ./static/uploads:/app/static/uploads <span class="se">\</span>
<span class="nt">-v</span> ./instance/hapi-h2-data:/app/h2-data <span class="se">\</span>
<span class="nt">-v</span> ./logs:/app/logs <span class="se">\</span>
<span class="nt">--name</span> fhirflare-lite <span class="se">\</span>
ghcr.io/sudo-jhare/fhirflare-ig-toolkit-lite:latest
Standalone Version <span class="o">(</span>Includes <span class="nb">local </span>HAPI FHIR<span class="o">)</span>:
</code></pre></div></div>
<p>Bash</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Pull the latest Standalone image</span>
docker pull ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest
<span class="c"># Run the Standalone version (maps ports 5000 and 8080)</span>
<span class="c"># You'll need to create local directories for persistent data first:</span>
<span class="c"># mkdir instance logs static static/uploads instance/hapi-h2-data</span>
docker run <span class="nt">-d</span> <span class="se">\</span>
<span class="nt">-p</span> 5000:5000 <span class="se">\</span>
<span class="nt">-p</span> 8080:8080 <span class="se">\</span>
<span class="nt">-v</span> ./instance:/app/instance <span class="se">\</span>
<span class="nt">-v</span> ./static/uploads:/app/static/uploads <span class="se">\</span>
<span class="nt">-v</span> ./instance/hapi-h2-data:/app/h2-data <span class="se">\</span>
<span class="nt">-v</span> ./logs:/app/logs <span class="se">\</span>
<span class="nt">--name</span> fhirflare-standalone <span class="se">\</span>
ghcr.io/sudo-jhare/fhirflare-ig-toolkit-standalone:latest
</code></pre></div></div>
<p>Building from Source (Developers)
Using Windows .bat Scripts (Standalone Version Only):</p>
<p>First Time Setup:</p>
<p>Run Build and Run for first time.bat:</p>
<p>Code snippet</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> <span class="s2">"&lt;project folder&gt;"</span>
git clone <span class="o">[</span>https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git]<span class="o">(</span>https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git<span class="o">)</span> hapi-fhir-jpaserver
copy .<span class="se">\\</span>hapi-fhir-Setup<span class="se">\\</span>target<span class="se">\\</span>classes<span class="se">\\</span>application.yaml .<span class="se">\\</span>hapi-fhir-jpaserver<span class="se">\\</span>target<span class="se">\\</span>classes<span class="se">\\</span>application.yaml
mvn clean package <span class="nt">-DskipTests</span><span class="o">=</span><span class="nb">true</span> <span class="nt">-Pboot</span>
docker-compose build <span class="nt">--no-cache</span>
docker-compose up <span class="nt">-d</span>
</code></pre></div></div>
<p>This clones the HAPI FHIR server, copies configuration, builds the project, and starts the containers.</p>
<p>Subsequent Runs:</p>
<p>Run Run.bat:</p>
<p>Code snippet</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> <span class="s2">"&lt;project folder&gt;"</span>
docker-compose up <span class="nt">-d</span>
</code></pre></div></div>
<p>This starts the Flask app (port 5000) and HAPI FHIR server (port 8080).</p>
<p>Access the Application:</p>
<ul>
<li>Flask UI: http://localhost:5000</li>
<li>HAPI FHIR server: http://localhost:8080</li>
<li>Manual Setup (Linux/MacOS/Windows):</li>
</ul>
<p>Preparation (Standalone Version Only):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> &lt;project folder&gt;
git clone <span class="o">[</span>https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git]<span class="o">(</span>https://github.com/hapifhir/hapi-fhir-jpaserver-starter.git<span class="o">)</span> hapi-fhir-jpaserver
<span class="nb">cp</span> ./hapi-fhir-Setup/target/classes/application.yaml ./hapi-fhir-jpaserver/target/classes/application.yaml
</code></pre></div></div>
<p>Build:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Build HAPI FHIR (Standalone Version Only)</span>
mvn clean package <span class="nt">-DskipTests</span><span class="o">=</span><span class="nb">true</span> <span class="nt">-Pboot</span>
<span class="c"># Build Docker Image (Specify APP_MODE=lite in docker-compose.yml for Lite version)</span>
docker-compose build <span class="nt">--no-cache</span>
</code></pre></div></div>
<p>Run:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose up <span class="nt">-d</span>
Access the Application:
</code></pre></div></div>
<ul>
<li>Flask UI: http://localhost:5000</li>
<li>HAPI FHIR server (Standalone only): http://localhost:8080</li>
<li>Local Development (Without Docker):</li>
</ul>
<p>Clone the Repository:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone <span class="o">[</span>https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git]<span class="o">(</span>https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git<span class="o">)</span>
<span class="nb">cd </span>FHIRFLARE-IG-Toolkit
</code></pre></div></div>
<p>Install Dependencies:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python <span class="nt">-m</span> venv venv
<span class="nb">source </span>venv/bin/activate <span class="c"># On Windows: venv\Scripts\activate</span>
pip <span class="nb">install</span> <span class="nt">-r</span> requirements.txt
</code></pre></div></div>
<p>Install Node.js, GoFSH, and SUSHI (for FSH Converter):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Example for Debian/Ubuntu</span>
curl <span class="nt">-fsSL</span> <span class="o">[</span>https://deb.nodesource.com/setup_18.x]<span class="o">(</span>https://deb.nodesource.com/setup_18.x<span class="o">)</span> | <span class="nb">sudo </span>bash -
<span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> nodejs
<span class="c"># Install globally</span>
npm <span class="nb">install</span> <span class="nt">-g</span> gofsh fsh-sushi
Set Environment Variables:
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">FLASK_SECRET_KEY</span><span class="o">=</span><span class="s1">'your-secure-secret-key'</span>
<span class="nb">export </span><span class="nv">API_KEY</span><span class="o">=</span><span class="s1">'your-api-key'</span>
<span class="c"># Optional: Set APP_MODE to 'lite' if desired</span>
<span class="c"># export APP_MODE='lite'</span>
</code></pre></div></div>
<p>Initialize Directories:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> instance static/uploads logs
<span class="c"># Ensure write permissions if needed</span>
<span class="c"># chmod -R 777 instance static/uploads logs</span>
</code></pre></div></div>
<p>Run the Application:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">FLASK_APP</span><span class="o">=</span>app.py
flask run
</code></pre></div></div>
<p>Access at http://localhost:5000.</p>
<p>Usage
Import an IG</p>
<h3 id="search-view-details-and-import-packages">Search, View Details, and Import Packages</h3>
<p>Navigate to <strong>Search and Import Packages</strong> (<code class="language-plaintext highlighter-rouge">/search-and-import</code>).</p>
<ol>
<li>The page will load a list of available FHIR Implementation Guide packages from a local cache or by fetching from configured registries.
<ul>
<li>A loading animation and progress messages are shown if fetching from registries.</li>
<li>The timestamp of the last cache update is displayed.</li>
</ul>
</li>
<li>Use the search bar to filter packages by name or author.</li>
<li>Packages are paginated for easier Browse.</li>
<li>For each package, you can:
<ul>
<li>View its latest official and absolute versions.</li>
<li>Click on the package name to navigate to a <strong>detailed view</strong> (<code class="language-plaintext highlighter-rouge">/package-details/&lt;name&gt;</code>) showing:
<ul>
<li>Comprehensive metadata (author, FHIR version, canonical URL, description).</li>
<li>A full list of available versions with publication dates.</li>
<li>Declared dependencies.</li>
<li>Other packages that depend on it (dependents).</li>
<li>Version history (logs).</li>
</ul>
</li>
<li>Directly import a specific version using the “Import” button on the search page or the details page.</li>
</ul>
</li>
<li><strong>Cache Management:</strong>
<ul>
<li>A “Clear &amp; Refresh Cache” button is available to trigger a background task (<code class="language-plaintext highlighter-rouge">/api/refresh-cache-task</code>) that clears the local database and in-memory cache and fetches the latest package information from all configured registries. Progress is shown via a live log.</li>
</ul>
</li>
</ol>
<ul>
<li>Enter a package name (e.g., hl7.fhir.au.core) and version (e.g., 1.1.0-preview).</li>
<li>Choose a dependency mode:</li>
<li>Current Recursive: Import all dependencies listed in package.json recursively.</li>
<li>Patch Canonical Versions: Import only canonical FHIR packages (e.g., hl7.fhir.r4.core).</li>
<li>Tree Shaking: Import only dependencies containing resources actually used by the main package.</li>
<li>Click Import to download the package and dependencies.</li>
</ul>
<p>Manage IGs
Go to Manage FHIR Packages (/view-igs) to view downloaded and processed IGs.</p>
<p>Actions:</p>
<ul>
<li>Process: Extract metadata (resource types, profiles, must-support elements, examples).</li>
<li>Unload: Remove processed IG data from the database.</li>
<li>Delete: Remove package files from the filesystem.</li>
</ul>
<p>Duplicates are highlighted for resolution.</p>
<p>View Processed IGs</p>
<p>After processing, view IG details (/view-ig/<id>), including:</id></p>
<ul>
<li>Resource types and profiles.</li>
<li>Must-support elements and examples.</li>
<li>Profile relationships (compliesWithProfile, imposeProfile) if enabled (DISPLAY_PROFILE_RELATIONSHIPS).</li>
</ul>
<p>Interactive StructureDefinition viewer (Differential, Snapshot, Must Support, Key Elements, Constraints, Terminology, Search Params).</p>
<ul>
<li>Validate FHIR Resources/Bundles</li>
<li>Navigate to Validate FHIR Sample (/validate-sample).</li>
</ul>
<p>Select a package (e.g., hl7.fhir.au.core#1.1.0-preview).</p>
<ul>
<li>Choose Single Resource or Bundle mode.</li>
<li>Paste or upload FHIR JSON/XML (e.g., a Patient resource).</li>
<li>Submit to view validation errors/warnings. Note: Alpha feature; report issues to GitHub (remove PHI).</li>
<li>Push IGs to a FHIR Server</li>
<li>Go to Push IGs (/push-igs).</li>
</ul>
<p>Select a downloaded package.</p>
<ul>
<li>Enter the Target FHIR Server URL.</li>
<li>Configure Authentication (None, Bearer Token).</li>
<li>Choose options: Include Dependencies, Force Upload (skips comparison check), Dry Run, Verbose Log.</li>
<li>Optionally filter by Resource Types (comma-separated) or Skip Specific Files (paths within package, comma/newline separated).</li>
<li>Click Push to FHIR Server to upload resources. Canonical resources are checked before upload. Identical resources are skipped unless Force Upload is checked.</li>
<li>Monitor progress in the live console.</li>
<li>Upload Test Data</li>
<li>Navigate to Upload Test Data (/upload-test-data).</li>
<li>Enter the Target FHIR Server URL.</li>
<li>Configure Authentication (None, Bearer Token).</li>
<li>Select one or more .json, .xml files, or a single .zip file containing test resources.</li>
<li>Optionally check Validate Resources Before Upload? and select a Validation Profile Package.</li>
<li>Choose Upload Mode:</li>
<li>Individual Resources: Uploads each resource one by one in dependency order.</li>
<li>Transaction Bundle: Uploads all resources in a single transaction.</li>
<li>Optionally check Use Conditional Upload (Individual Mode Only)? to use If-Match headers for updates.</li>
<li>Choose Error Handling:</li>
<li>Stop on First Error: Halts the process if any validation or upload fails.</li>
<li>Continue on Error: Reports errors but attempts to process/upload remaining resources.</li>
<li>Click Upload and Process. The tool parses files, optionally validates, analyzes dependencies, topologically sorts resources, and uploads them according to selected options.</li>
<li>Monitor progress in the streaming log output.</li>
</ul>
<p>Convert FHIR to FSH</p>
<ul>
<li>Navigate to FSH Converter (/fsh-converter).</li>
</ul>
<p>Optionally select a package for context (e.g., hl7.fhir.au.core#1.1.0-preview).
Choose input mode:
Upload File: Upload a FHIR JSON/XML file.
Paste Text: Paste FHIR JSON/XML content.
Configure options:
Output Style: file-per-definition, group-by-fsh-type, group-by-profile, single-file.
Log Level: error, warn, info, debug.
FHIR Version: R4, R4B, R5, or auto-detect.
Fishing Trip: Enable round-trip validation with SUSHI, generating a comparison report.
Dependencies: Specify additional packages (e.g., hl7.fhir.us.core@6.1.0, one per line).
Indent Rules: Enable context path indentation for readable FSH.
Meta Profile: Choose only-one, first, or none for meta.profile handling.
Alias File: Upload an FSH file with aliases (e.g., $MyAlias = http://example.org).
No Alias: Disable automatic alias generation.
Click Convert to FSH to generate and display FSH output, with a waiting spinner (light/dark theme) during processing.
If Fishing Trip is enabled, view the comparison report via the “Click here for SUSHI Validation” badge button.
Download the result as a .fsh file.
Retrieve and Split Bundles
Navigate to Retrieve/Split Data (/retrieve-split-data).</p>
<p>Retrieve Bundles from Server:</p>
<ul>
<li>Enter the FHIR Server URL (defaults to the proxy if empty).</li>
<li>Select one or more Resource Types to retrieve (e.g., Patient, Observation).</li>
<li>Optionally check Fetch Referenced Resources.</li>
<li>If checked, further optionally check Fetch Full Reference Bundles to retrieve entire bundles for each referenced type (e.g., all Patients if a Patient is referenced) instead of individual resources by ID.</li>
<li>Click Retrieve Bundles.</li>
<li>Monitor progress in the streaming log. A ZIP file containing the retrieved bundles/resources will be prepared for download.</li>
</ul>
<p>Split Uploaded Bundles:</p>
<ul>
<li>Upload a ZIP file containing FHIR bundles (JSON format).</li>
<li>Click Split Bundles.</li>
<li>A ZIP file containing individual resources extracted from the bundles will be prepared for download.</li>
<li>Explore FHIR Operations</li>
<li>Navigate to FHIR UI Operations (/fhir-ui-operations).</li>
</ul>
<p>Toggle between local HAPI (/fhir) or a custom FHIR server.</p>
<ul>
<li>Click Fetch Metadata to load the servers CapabilityStatement.</li>
<li>Select a resource type (e.g., Patient, Observation) or System to view operations:</li>
<li>System operations: GET /metadata, POST /, GET /_history, GET/POST /$diff, POST /$reindex, POST /$expunge, etc.</li>
<li>Resource operations: GET Patient/:id, POST Observation/_search, etc.</li>
<li>Use Try it out to input parameters or request bodies, then Execute to view results in JSON, XML, or narrative formats.</li>
</ul>
<h3 id="configure-embedded-hapi-fhir-server-standalone-mode">Configure Embedded HAPI FHIR Server (Standalone Mode)</h3>
<p>For users running the <strong>Standalone version</strong>, which includes an embedded HAPI FHIR server.</p>
<ol>
<li>Navigate to <strong>Configure HAPI FHIR</strong> (<code class="language-plaintext highlighter-rouge">/config-hapi</code>).</li>
<li>The page displays the content of the HAPI FHIR servers <code class="language-plaintext highlighter-rouge">application.yaml</code> file.</li>
<li>You can edit the configuration directly in the text area.
<ul>
<li><em>Caution: Incorrect modifications can break the HAPI FHIR server.</em></li>
</ul>
</li>
<li>Click <strong>Save Configuration</strong> to apply your changes to the <code class="language-plaintext highlighter-rouge">application.yaml</code> file.</li>
<li>Click <strong>Restart Tomcat</strong> to restart the HAPI FHIR server and load the new configuration. The restart process may take a few moments.</li>
</ol>
<p>API Usage
Import IG
Bash</p>
<p>curl -X POST http://localhost:5000/api/import-ig <br />
-H “Content-Type: application/json” <br />
-H “X-API-Key: your-api-key” <br />
-d {“package_name”: “hl7.fhir.au.core”, “version”: “1.1.0-preview”, “dependency_mode”: “recursive”}
Returns complies_with_profiles, imposed_profiles, and duplicate_packages_present info.</p>
<h3 id="refresh-package-cache-background-task">Refresh Package Cache (Background Task)</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://localhost:5000/api/refresh-cache-task <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"X-API-Key: your-api-key"</span>
</code></pre></div></div>
<p>Push IG
Bash</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://localhost:5000/api/push-ig <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Accept: application/x-ndjson"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"X-API-Key: your-api-key"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{
"package_name": "hl7.fhir.au.core",
"version": "1.1.0-preview",
"fhir_server_url": "http://localhost:8080/fhir",
"include_dependencies": true,
"force_upload": false,
"dry_run": false,
"verbose": false,
"auth_type": "none"
}'</span>
</code></pre></div></div>
<p>Returns a streaming NDJSON response with progress and final summary.</p>
<p>Upload Test Data
Bash</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://localhost:5000/api/upload-test-data <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"X-API-Key: your-api-key"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Accept: application/x-ndjson"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"fhir_server_url=http://your-fhir-server/fhir"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"auth_type=bearerToken"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"auth_token=YOUR_TOKEN"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"upload_mode=individual"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"error_handling=continue"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"validate_before_upload=true"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"validation_package_id=hl7.fhir.r4.core#4.0.1"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"use_conditional_uploads=true"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"test_data_files=@/path/to/your/patient.json"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"test_data_files=@/path/to/your/observations.zip"</span>
</code></pre></div></div>
<p>Returns a streaming NDJSON response with progress and final summary. Uses multipart/form-data for file uploads.</p>
<p>Retrieve Bundles
Bash</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://localhost:5000/api/retrieve-bundles <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"X-API-Key: your-api-key"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Accept: application/x-ndjson"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"fhir_server_url=http://your-fhir-server/fhir"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"resources=Patient"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"resources=Observation"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"validate_references=true"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"fetch_reference_bundles=false"</span>
</code></pre></div></div>
<p>Returns a streaming NDJSON response with progress. The X-Zip-Path header in the final response part will contain the path to download the ZIP archive (e.g., /tmp/retrieved_bundles_datetime.zip).</p>
<p>Split Bundles
Bash</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://localhost:5000/api/split-bundles <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"X-API-Key: your-api-key"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Accept: application/x-ndjson"</span> <span class="se">\</span>
<span class="nt">-F</span> <span class="s2">"split_bundle_zip_path=@/path/to/your/bundles.zip"</span>
</code></pre></div></div>
<p>Returns a streaming NDJSON response. The X-Zip-Path header in the final response part will contain the path to download the ZIP archive of split resources.</p>
<p>Validate Resource/Bundle
Not yet exposed via API; use the UI at /validate-sample.</p>
<p>Configuration Options
Located in app.py:</p>
<ul>
<li>VALIDATE_IMPOSED_PROFILES: (Default: True) Validates resources against imposed profiles during push.</li>
<li>DISPLAY_PROFILE_RELATIONSHIPS: (Default: True) Shows compliesWithProfile and imposeProfile in the UI.</li>
<li>FHIR_PACKAGES_DIR: (Default: /app/instance/fhir_packages) Stores .tgz packages and metadata.</li>
<li>UPLOAD_FOLDER: (Default: /app/static/uploads) Stores GoFSH output files and FSH comparison reports.</li>
<li>SECRET_KEY: Required for CSRF protection and sessions. Set via environment variable or directly.</li>
<li>API_KEY: Required for API authentication. Set via environment variable or directly.</li>
<li>MAX_CONTENT_LENGTH: (Default: Flask default) Max size for HTTP request body (e.g., 16 * 1024 * 1024 for 16MB). Important for large uploads.</li>
<li>MAX_FORM_PARTS: (Default: Werkzeug default, often 1000) Default max number of form parts. Overridden for /api/upload-test-data by CustomFormDataParser.</li>
</ul>
<h3 id="get-hapi-fhir-configuration-standalone-mode">Get HAPI FHIR Configuration (Standalone Mode)</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> GET http://localhost:5000/api/config <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"X-API-Key: your-api-key"</span>
</code></pre></div></div>
<p>Save HAPI FHIR Configuration:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://localhost:5000/api/config <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"X-API-Key: your-api-key"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{"your_yaml_key": "your_value", ...}'</span> <span class="c"># Send the full YAML content as JSON</span>
</code></pre></div></div>
<p>Restart HAPI FHIR Server:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> POST http://localhost:5000/api/restart-tomcat <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"X-API-Key: your-api-key"</span>
</code></pre></div></div>
<p>Testing
The project includes a test suite covering UI, API, database, file operations, and security.</p>
<p>Test Prerequisites:</p>
<p>pytest: For running tests.
pytest-mock: For mocking dependencies. Install: pip install pytest pytest-mock
Running Tests:</p>
<p>Bash</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> &lt;project folder&gt;
pytest tests/test_app.py <span class="nt">-v</span>
</code></pre></div></div>
<p>Test Coverage:</p>
<ul>
<li>UI Pages: Homepage, Import IG, Manage IGs, Push IGs, Validate Sample, View Processed IG, FSH Converter, Upload Test Data, Retrieve/Split Data.</li>
<li>API Endpoints: POST /api/import-ig, POST /api/push-ig, GET /get-structure, GET /get-example, POST /api/upload-test-data, POST /api/retrieve-bundles, POST /api/split-bundles.</li>
<li>Database: IG processing, unloading, viewing.</li>
<li>File Operations: Package processing, deletion, FSH output, ZIP handling.</li>
<li>Security: CSRF protection, flash messages, secret key.</li>
<li>FSH Converter: Form submission, file/text input, GoFSH execution, Fishing Trip comparison.</li>
<li>Upload Test Data: Parsing, dependency graph, sorting, upload modes, validation, conditional uploads.</li>
</ul>
<p>Development Notes</p>
<p>Background</p>
<p>The toolkit addresses the need for a comprehensive FHIR IG management tool, with recent enhancements for resource validation, FSH conversion with advanced GoFSH features, flexible versioning, improved IG pushing, dependency-aware test data uploading, and bundle retrieval/splitting, making it a versatile platform for FHIR developers.</p>
<p>Technical Decisions</p>
<ul>
<li>Flask: Lightweight and flexible for web development.</li>
<li>SQLite: Simple for development; consider PostgreSQL for production.</li>
<li>Bootstrap 5.3.3: Responsive UI with custom styling.</li>
<li>Lottie-Web: Renders themed animations for FSH conversion waiting spinner.</li>
<li>GoFSH/SUSHI: Integrated via Node.js for advanced FSH conversion and round-trip validation.</li>
<li>Docker: Ensures consistent deployment with Flask and HAPI FHIR.</li>
<li>Flexible Versioning: Supports non-standard IG versions (e.g., -preview, -ballot).</li>
<li>Live Console/Streaming: Real-time feedback for complex operations (Push, Upload Test Data, FSH, Retrieve Bundles).</li>
<li>Validation: Alpha feature with ongoing FHIRPath improvements.</li>
<li>Dependency Management: Uses topological sort for Upload Test Data feature.</li>
<li>Form Parsing: Uses custom Werkzeug parser for Upload Test Data to handle large numbers of files.</li>
</ul>
<p>Recent Updates</p>
<ul>
<li>Enhanced package search page with caching, detailed views (dependencies, dependents, version history), and background cache refresh.</li>
<li>Upload Test Data Enhancements (April 2025):</li>
<li>Added optional Pre-Upload Validation against selected IG profiles.</li>
<li>Added optional Conditional Uploads (GET + POST/PUT w/ If-Match) for individual mode.</li>
<li>Implemented robust XML parsing using fhir.resources library (when available).</li>
<li>Fixed 413 Request Entity Too Large errors for large file counts using a custom Werkzeug FormDataParser.</li>
<li>Path: templates/upload_test_data.html, app.py, services.py, forms.py.</li>
<li>Push IG Enhancements (April 2025):</li>
<li>Added semantic comparison to skip uploading identical resources.</li>
<li>Added “Force Upload” option to bypass comparison.</li>
<li>Improved handling of canonical resources (search before PUT/POST).</li>
<li>Added filtering by specific files to skip during push.</li>
<li>More detailed summary report in stream response.</li>
<li>Path: templates/cp_push_igs.html, app.py, services.py.</li>
<li>Waiting Spinner for FSH Converter (April 2025):</li>
<li>Added a themed (light/dark) Lottie animation spinner during FSH execution.</li>
<li>Path: templates/fsh_converter.html, static/animations/, static/js/lottie-web.min.js.</li>
<li>Advanced FSH Converter (April 2025):</li>
<li>Added support for GoFSH advanced options: fshing-trip, dependency, indent, meta-profile, alias-file, no-alias.</li>
<li>Displays Fishing Trip comparison reports.</li>
<li>Path: templates/fsh_converter.html, app.py, services.py, forms.py.</li>
<li>(New) Retrieve and Split Data (May 2025):</li>
<li>Added UI and API for retrieving bundles from a FHIR server by resource type.</li>
<li>Added options to fetch referenced resources (individually or as full type bundles).</li>
<li>Added functionality to split uploaded ZIP files of bundles into individual resources.</li>
<li>Streaming log for retrieval and ZIP download for results.</li>
<li>Paths: templates/retrieve_split_data.html, app.py, services.py, forms.py.</li>
<li>Known Issues and Workarounds</li>
<li>Favicon 404: Clear browser cache or verify /app/static/favicon.ico.</li>
<li>CSRF Errors: Set FLASK_SECRET_KEY and ensure in forms.</li>
<li>Import Fails: Check package name/version and connectivity.</li>
<li>Validation Accuracy: Alpha feature; report issues to GitHub (remove PHI).</li>
<li>Package Parsing: Non-standard .tgz filenames may parse incorrectly. Fallback uses name-only parsing.</li>
<li>Permissions: Ensure instance/ and static/uploads/ are writable.</li>
<li>GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation.</li>
<li>Upload Test Data XML Parsing: Relies on fhir.resources library for full validation; basic parsing used as fallback. Complex XML structures might not be fully analyzed for dependencies with basic parsing. Prefer JSON for reliable dependency analysis.</li>
<li>413 Request Entity Too Large: Primarily handled by CustomFormDataParser for /api/upload-test-data. Check the parsers max_form_parts limit if still occurring. MAX_CONTENT_LENGTH in app.py controls overall size. Reverse proxy limits (client_max_body_size in Nginx) might also apply.</li>
</ul>
<p>Future Improvements</p>
<ul>
<li>Upload Test Data: Improve XML parsing further (direct XML-&gt;fhir.resource object if possible), add visual progress bar, add upload order preview, implement transaction bundle size splitting, add Clear Target Server option (with confirmation).</li>
<li>Validation: Enhance FHIRPath for complex constraints; add API endpoint.</li>
<li>Sorting: Sort IG versions in /view-igs (e.g., ascending).</li>
<li>Duplicate Resolution: Options to keep latest version or merge resources.</li>
<li>Production Database: Support PostgreSQL.</li>
<li>Error Reporting: Detailed validation error paths in the UI.</li>
<li>FSH Enhancements: Add API endpoint for FSH conversion; support inline instance construction.</li>
<li>FHIR Operations: Add complex parameter support (e.g., /$diff with left/right).</li>
<li>Retrieve/Split Data: Add option to filter resources during retrieval (e.g., by date, specific IDs).</li>
</ul>
<p>Completed Items</p>
<ul>
<li>Testing suite with basic coverage.</li>
<li>API endpoints for POST /api/import-ig and POST /api/push-ig.</li>
<li>Flexible versioning (-preview, -ballot).</li>
<li>CSRF fixes for forms.</li>
<li>Resource validation UI (alpha).</li>
<li>FSH Converter with advanced GoFSH features and waiting spinner.</li>
<li>Push IG enhancements (force upload, semantic comparison, canonical handling, skip files).</li>
<li>Upload Test Data feature with dependency sorting, multiple upload modes, pre-upload validation, conditional uploads, robust XML parsing, and fix for large file counts.</li>
<li>Retrieve and Split Data functionality with reference fetching and ZIP download.</li>
<li>Far-Distant Improvements</li>
<li>Cache Service: Use Redis for IG metadata caching.</li>
<li>Database Optimization: Composite index on ProcessedIg.package_name and ProcessedIg.version.</li>
</ul>
<p>Directory Structure</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FHIRFLARE-IG-Toolkit/
├── app.py <span class="c"># Main Flask application</span>
├── Build and Run <span class="k">for </span>first time.bat <span class="c"># Windows script for first-time Docker setup</span>
├── docker-compose.yml <span class="c"># Docker Compose configuration</span>
├── Dockerfile <span class="c"># Docker configuration</span>
├── forms.py <span class="c"># Form definitions</span>
├── LICENSE.md <span class="c"># Apache 2.0 License</span>
├── README.md <span class="c"># Project documentation</span>
├── requirements.txt <span class="c"># Python dependencies</span>
├── Run.bat <span class="c"># Windows script for running Docker</span>
├── services.py <span class="c"># Logic for IG import, processing, validation, pushing, FSH conversion, test data upload, retrieve/split</span>
├── supervisord.conf <span class="c"># Supervisor configuration</span>
├── hapi-fhir-Setup/
│ ├── README.md <span class="c"># HAPI FHIR setup instructions</span>
│ └── target/
│ └── classes/
│ └── application.yaml <span class="c"># HAPI FHIR configuration</span>
├── instance/
│ ├── fhir_ig.db <span class="c"># SQLite database</span>
│ ├── fhir_ig.db.old <span class="c"># Database backup</span>
│ └── fhir_packages/ <span class="c"># Stored IG packages and metadata</span>
│ ├── ... <span class="o">(</span>example packages<span class="o">)</span> ...
├── logs/
│ ├── flask.log <span class="c"># Flask application logs</span>
│ ├── flask_err.log <span class="c"># Flask error logs</span>
│ ├── supervisord.log <span class="c"># Supervisor logs</span>
│ ├── supervisord.pid <span class="c"># Supervisor PID file</span>
│ ├── tomcat.log <span class="c"># Tomcat logs for HAPI FHIR</span>
│ └── tomcat_err.log <span class="c"># Tomcat error logs</span>
├── static/
│ ├── animations/
│ │ ├── loading-dark.json <span class="c"># Dark theme spinner animation</span>
│ │ └── loading-light.json <span class="c"># Light theme spinner animation</span>
│ ├── favicon.ico <span class="c"># Application favicon</span>
│ ├── FHIRFLARE.png <span class="c"># Application logo</span>
│ ├── js/
│ │ └── lottie-web.min.js <span class="c"># Lottie library for spinner</span>
│ └── uploads/
│ ├── output.fsh <span class="c"># Generated FSH output (temp location)</span>
│ └── fsh_output/ <span class="c"># GoFSH output directory</span>
│ ├── ... <span class="o">(</span>example GoFSH output<span class="o">)</span> ...
├── templates/
│ ├── base.html <span class="c"># Base template</span>
│ ├── cp_downloaded_igs.html <span class="c"># UI for managing IGs</span>
│ ├── cp_push_igs.html <span class="c"># UI for pushing IGs</span>
│ ├── cp_view_processed_ig.html <span class="c"># UI for viewing processed IGs</span>
│ ├── fhir_ui.html <span class="c"># UI for FHIR API explorer</span>
│ ├── fhir_ui_operations.html <span class="c"># UI for FHIR server operations</span>
│ ├── fsh_converter.html <span class="c"># UI for FSH conversion</span>
│ ├── import_ig.html <span class="c"># UI for importing IGs</span>
│ ├── index.html <span class="c"># Homepage</span>
│ ├── retrieve_split_data.html <span class="c"># UI for Retrieve and Split Data</span>
│ ├── upload_test_data.html <span class="c"># UI for Uploading Test Data</span>
│ ├── validate_sample.html <span class="c"># UI for validating resources/bundles</span>
│ ├── config_hapi.html <span class="c"># UI for HAPI FHIR Configuration</span>
│ └── _form_helpers.html <span class="c"># Form helper macros</span>
├── tests/
│ └── test_app.py <span class="c"># Test suite</span>
└── hapi-fhir-jpaserver/ <span class="c"># HAPI FHIR server resources (if Standalone)</span>
</code></pre></div></div>
<p>Contributing</p>
<ol>
<li>Fork the repository.</li>
<li>Create a feature branch (git checkout -b feature/your-feature).</li>
<li>Commit changes (git commit -m “Add your feature”).</li>
<li>Push to your branch (git push origin feature/your-feature).</li>
<li>Open a Pull Request.</li>
<li>Ensure code follows PEP 8 and includes tests in tests/test_app.py.</li>
</ol>
<p>Troubleshooting</p>
<ul>
<li>Favicon 404: Clear browser cache or verify /app/static/favicon.ico: docker exec -it <container_name> curl http://localhost:5000/static/favicon.ico</container_name></li>
<li>CSRF Errors: Set FLASK_SECRET_KEY and ensure in forms.</li>
<li>Import Fails: Check package name/version and connectivity.</li>
<li>Validation Accuracy: Alpha feature; report issues to GitHub (remove PHI).</li>
<li>Package Parsing: Non-standard .tgz filenames may parse incorrectly. Fallback uses name-only parsing.</li>
<li>Permissions: Ensure instance/ and static/uploads/ are writable: chmod -R 777 instance static/uploads logs</li>
<li>GoFSH/SUSHI Errors: Check ./logs/flask_err.log for ERROR:services:GoFSH failed. Ensure valid FHIR inputs and SUSHI installation: docker exec -it <container_name> sushi --version</container_name></li>
<li>413 Request Entity Too Large: Increase MAX_CONTENT_LENGTH and MAX_FORM_PARTS in app.py. If using a reverse proxy (e.g., Nginx), increase its client_max_body_size setting as well. Ensure the application/container is fully restarted/rebuilt.</li>
</ul>
<p>License</p>
<p>Licensed under the Apache 2.0 License. See LICENSE.md for details.</p>
</section>
</div>
<footer>
<p>Project maintained by <a href="https://github.com/Sudo-JHare">Sudo-JHare</a></p>
<p>Hosted on GitHub Pages &mdash; Theme by <a href="https://github.com/orderedlist">orderedlist</a></p>
</footer>
<!--[if !IE]><script>fixScale(document);</script><![endif]-->
</body>
</html>

137
assets/css/style.css Normal file
View File

@ -0,0 +1,137 @@
/* generated by rouge http://rouge.jneen.net/ original base16 by Chris Kempson (https://github.com/chriskempson/base16)
*/
@import url("https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700");
.highlight table td { padding: 5px; }
.highlight table pre { margin: 0; }
.highlight, .highlight .w { color: #d0d0d0; }
.highlight .err { color: #151515; background-color: #ac4142; }
.highlight .c, .highlight .cd, .highlight .cm, .highlight .c1, .highlight .cs { color: #888; }
.highlight .cp { color: #f4bf75; }
.highlight .nt { color: #f4bf75; }
.highlight .o, .highlight .ow { color: #d0d0d0; }
.highlight .p, .highlight .pi { color: #d0d0d0; }
.highlight .gi { color: #90a959; }
.highlight .gd { color: #ac4142; }
.highlight .gh { color: #6a9fb5; font-weight: bold; }
.highlight .k, .highlight .kn, .highlight .kp, .highlight .kr, .highlight .kv { color: #aa759f; }
.highlight .kc { color: #d28445; }
.highlight .kt { color: #d28445; }
.highlight .kd { color: #d28445; }
.highlight .s, .highlight .sb, .highlight .sc, .highlight .sd, .highlight .s2, .highlight .sh, .highlight .sx, .highlight .s1 { color: #90a959; }
.highlight .sr { color: #75b5aa; }
.highlight .si { color: #8f5536; }
.highlight .se { color: #8f5536; }
.highlight .nn { color: #f4bf75; }
.highlight .nc { color: #f4bf75; }
.highlight .no { color: #f4bf75; }
.highlight .na { color: #6a9fb5; }
.highlight .m, .highlight .mf, .highlight .mh, .highlight .mi, .highlight .il, .highlight .mo, .highlight .mb, .highlight .mx { color: #90a959; }
.highlight .ss { color: #90a959; }
html { background: #6C7989; background: #6C7989 linear-gradient(#6C7989, #434B55) fixed; height: 100%; }
body { padding: 50px 0; margin: 0; font: 14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; color: #555; font-weight: 300; background: url("../images/checker.png") fixed; min-height: calc(100% - 100px); }
.wrapper { width: 740px; margin: 0 auto; background: #DEDEDE; border-radius: 8px; box-shadow: rgba(0, 0, 0, 0.2) 0 0 0 1px, rgba(0, 0, 0, 0.45) 0 3px 10px; }
header, section, footer { display: block; }
a { color: #069; text-decoration: none; }
p { margin: 0 0 20px; padding: 0; }
strong { color: #222; font-weight: 700; }
header { border-radius: 8px 8px 0 0; background: #C6EAFA; background: linear-gradient(#DDFBFC, #C6EAFA); position: relative; padding: 15px 20px; border-bottom: 1px solid #B2D2E1; }
header h1 { margin: 0; padding: 0; font-size: 24px; line-height: 1.2; color: #069; text-shadow: rgba(255, 255, 255, 0.9) 0 1px 0; }
header.without-description h1 { margin: 10px 0; }
header p { margin: 0; color: #61778B; width: 300px; font-size: 13px; }
header p.view { display: none; font-weight: 700; text-shadow: rgba(255, 255, 255, 0.9) 0 1px 0; -webkit-font-smoothing: antialiased; }
header p.view a { color: #06c; }
header p.view small { font-weight: 400; }
header ul { margin: 0; padding: 0; list-style: none; position: absolute; z-index: 1; right: 20px; top: 20px; height: 38px; padding: 1px 0; background: #5198DF; background: linear-gradient(#77B9FB, #3782CD); border-radius: 5px; box-shadow: inset rgba(255, 255, 255, 0.45) 0 1px 0, inset rgba(0, 0, 0, 0.2) 0 -1px 0; width: auto; }
header ul:before { content: ''; position: absolute; z-index: -1; left: -5px; top: -4px; right: -5px; bottom: -6px; background: rgba(0, 0, 0, 0.1); border-radius: 8px; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0, inset rgba(255, 255, 255, 0.7) 0 -1px 0; }
header ul li { width: 79px; float: left; border-right: 1px solid #3A7CBE; height: 38px; }
header ul li.single { border: none; }
header ul li + li { width: 78px; border-left: 1px solid #8BBEF3; }
header ul li + li + li { border-right: none; width: 79px; }
header ul a { line-height: 1; font-size: 11px; color: #fff; color: rgba(255, 255, 255, 0.8); display: block; text-align: center; font-weight: 400; padding-top: 6px; height: 40px; text-shadow: rgba(0, 0, 0, 0.4) 0 -1px 0; }
header ul a strong { font-size: 14px; display: block; color: #fff; -webkit-font-smoothing: antialiased; }
section { padding: 15px 20px; font-size: 15px; border-top: 1px solid #fff; background: linear-gradient(#fafafa, #DEDEDE 700px); border-radius: 0 0 8px 8px; position: relative; }
h1, h2, h3, h4, h5, h6 { color: #222; padding: 0; margin: 0 0 20px; line-height: 1.2; }
p, ul, ol, table, pre, dl { margin: 0 0 20px; }
h1, h2, h3 { line-height: 1.1; }
h1 { font-size: 28px; }
h2 { color: #393939; }
h3, h4, h5, h6 { color: #494949; }
blockquote { margin: 0 -20px 20px; padding: 15px 20px 1px 40px; font-style: italic; background: #ccc; background: rgba(0, 0, 0, 0.06); color: #222; }
img { max-width: 100%; }
code, pre { font-family: Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; color: #333; font-size: 12px; overflow-x: auto; }
pre { padding: 20px; background: #3A3C42; color: #f8f8f2; margin: 0 -20px 20px; }
pre code { color: #f8f8f2; }
li pre { margin-left: -60px; padding-left: 60px; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 5px 10px; border-bottom: 1px solid #aaa; }
dt { color: #222; font-weight: 700; }
th { color: #222; }
small { font-size: 11px; }
hr { border: 0; background: #aaa; height: 1px; margin: 0 0 20px; }
kbd { background-color: #fafbfc; border: 1px solid #c6cbd1; border-bottom-color: #959da5; border-radius: 3px; box-shadow: inset 0 -1px 0 #959da5; color: #444d56; display: inline-block; font-size: 11px; line-height: 10px; padding: 3px 5px; vertical-align: middle; }
footer { width: 640px; margin: 0 auto; padding: 20px 0 0; color: #ccc; overflow: hidden; }
footer a { color: #fff; font-weight: bold; }
footer p { float: left; }
footer p + p { float: right; }
@media print, screen and (max-width: 740px) { body { padding: 0; }
.wrapper { border-radius: 0; box-shadow: none; width: 100%; }
footer { border-radius: 0; padding: 20px; width: auto; }
footer p { float: none; margin: 0; }
footer p + p { float: none; } }
@media print, screen and (max-width: 580px) { header ul { display: none; }
header p.view { display: block; }
header p { width: 100%; } }
@media print { header p.view a small:before { content: 'at https://github.com/'; } }

BIN
assets/images/checker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

20
assets/js/scale.fix.js Normal file
View File

@ -0,0 +1,20 @@
fixScale = function(doc) {
var addEvent = 'addEventListener',
type = 'gesturestart',
qsa = 'querySelectorAll',
scales = [1, 1],
meta = qsa in doc ? doc[qsa]('meta[name=viewport]') : [];
function fix() {
meta.content = 'width=device-width,minimum-scale=' + scales[0] + ',maximum-scale=' + scales[1];
doc.removeEventListener(type, fix, true);
}
if ((meta = meta[meta.length - 1]) && addEvent in doc) {
fix();
scales = [.25, 1.6];
doc[addEvent](type, fix, true);
}
};

92
index.html Normal file
View File

@ -0,0 +1,92 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Begin Jekyll SEO tag v2.8.0 -->
<title>FHIRFLARE IG Toolkit | Helm chart for deploying the fhirflare-ig-toolkit application</title>
<meta name="generator" content="Jekyll v3.9.5" />
<meta property="og:title" content="FHIRFLARE IG Toolkit" />
<meta property="og:locale" content="en_US" />
<meta name="description" content="Helm chart for deploying the fhirflare-ig-toolkit application" />
<meta property="og:description" content="Helm chart for deploying the fhirflare-ig-toolkit application" />
<meta property="og:site_name" content="FHIRFLARE IG Toolkit" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="FHIRFLARE IG Toolkit" />
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"WebSite","description":"Helm chart for deploying the fhirflare-ig-toolkit application","headline":"FHIRFLARE IG Toolkit","name":"FHIRFLARE IG Toolkit","url":"/"}</script>
<!-- End Jekyll SEO tag -->
<link rel="stylesheet" href="/assets/css/style.css?v=5277cd16f4e98fb60f64f9252e3d3fc64b43b548">
<script src="/assets/js/scale.fix.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<!-- start custom head snippets, customize with your own _includes/head-custom.html file -->
<!-- Setup Google Analytics -->
<!-- You can set your favicon here -->
<!-- link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" -->
<!-- end custom head snippets -->
</head>
<body>
<div class="wrapper">
<header >
<h1>FHIRFLARE IG Toolkit</h1>
<p>Helm chart for deploying the fhirflare-ig-toolkit application</p>
<p class="view"><a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit">View the Project on GitHub <small></small></a></p>
<ul>
<li><a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/zipball/gh-pages">Download <strong>ZIP File</strong></a></li>
<li><a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit/tarball/gh-pages">Download <strong>TAR Ball</strong></a></li>
<li><a href="https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit">View On <strong>GitHub</strong></a></li>
</ul>
</header>
<section>
<h1 id="fhirflare-ig-toolkit">FHIRFLARE IG Toolkit</h1>
<p>Helm chart for deploying the fhirflare-ig-toolkit application</p>
<h2 id="overview">Overview</h2>
<p>FHIRFLARE-IG-Toolkit is a comprehensive solution for working with FHIR Implementation Guides.</p>
<h2 id="features">Features</h2>
<ul>
<li>Helm chart deployment</li>
<li>FHIR resources management</li>
<li>Implementation Guide toolkit</li>
</ul>
<h2 id="getting-started">Getting Started</h2>
<p>Check out the <a href="/README.html">documentation</a> to get started with FHIRFLARE IG Toolkit.</p>
</section>
</div>
<footer>
<p>Project maintained by <a href="https://github.com/Sudo-JHare">Sudo-JHare</a></p>
<p>Hosted on GitHub Pages &mdash; Theme by <a href="https://github.com/orderedlist">orderedlist</a></p>
</footer>
<!--[if !IE]><script>fixScale(document);</script><![endif]-->
</body>
</html>

BIN
static/FHIRFLARE.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 KiB

BIN
static/FHIRFLARE.png.old Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

390
static/css/animation.css Normal file
View File

@ -0,0 +1,390 @@
body {
width: 100%;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* Apply overflow and height constraints only for fire animation page
body.fire-animation-page {
overflow: hidden;
height: 100vh;
}
*/
/* Fire animation overlay */
.fire-on {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(#1d4456, #112630);
opacity: 1;
z-index: 1;
transition: all 1200ms linear;
}
.section-center {
position: relative;
width: 300px; /* Reduced size for landing page */
height: 300px;
margin: 0 auto;
display: block;
overflow: hidden;
border: 8px solid rgba(0,0,0,.2);
border-radius: 50%;
z-index: 5;
background-color: #1d4456;
box-shadow: 0 0 50px 5px rgba(255,148,0,.1);
transition: all 500ms linear;
}
/* Wood and star using local images */
.wood {
position: absolute;
z-index: 21;
left: 50%;
bottom: 12%;
width: 80px;
margin-left: -40px;
height: 30px;
background-image: url('{{ url_for('static', filename='img/wood.png') }}');
background-size: 80px 30px;
border-radius: 5px;
}
.star {
z-index: 2;
position: absolute;
top: 138px;
left: 18px;
background-image: url('{{ url_for('static', filename='img/star.png') }}');
background-size: 11px 11px;
width: 11px;
height: 11px;
opacity: 0.4;
animation: starShine 3.5s linear infinite;
transition: all 1200ms linear;
}
.wood-circle {
position: absolute;
z-index: 20;
left: 50%;
bottom: 11%;
width: 100px;
margin-left: -50px;
height: 20px;
border-radius: 100%;
background-color: #0a171d;
}
.circle {
position: absolute;
z-index: 6;
right: -225px;
bottom: -337px;
width: 562px;
height: 525px;
border-radius: 100%;
background-color: #112630;
}
/* Moon */
.moon {
position: absolute;
top: 37px;
left: 86px;
width: 60px;
height: 60px;
background-color: #b2b7bc;
border-radius: 50%;
box-shadow: inset -15px 1.5px 0 0px #c0c3c9, 0 0 7px 3px rgba(228,228,222,.4);
z-index: 1;
animation: brilla-moon 4s alternate infinite;
transition: all 2000ms linear;
}
.moon div:nth-child(1) {
position: absolute;
top: 50%;
left: 10%;
width: 12%;
height: 12%;
border-radius: 50%;
border: 1px solid #adaca2;
box-shadow: inset 1.5px -0.75px 0 0px #85868b;
opacity: 0.4;
}
.moon div:nth-child(2) {
position: absolute;
top: 20%;
left: 38%;
width: 16%;
height: 16%;
border-radius: 50%;
border: 1px solid #adaca2;
box-shadow: inset 1.5px -0.75px 0 0px #85868b;
opacity: 0.4;
}
.moon div:nth-child(3) {
position: absolute;
top: 60%;
left: 45%;
width: 20%;
height: 20%;
border-radius: 50%;
border: 1px solid #adaca2;
box-shadow: inset 1.5px -0.75px 0 0px #85868b;
opacity: 0.4;
}
@keyframes brilla-moon {
0% { box-shadow: inset -15px 1.5px 0 0px #c0c3c9, 0 0 7px 3px rgba(228,228,222,.4); }
50% { box-shadow: inset -15px 1.5px 0 0px #c0c3c9, 0 0 11px 6px rgba(228,228,222,.4); }
}
/* Shooting stars */
.shooting-star {
z-index: 2;
width: 1px;
height: 37px;
border-bottom-left-radius: 50%;
border-bottom-right-radius: 50%;
position: absolute;
top: 0;
left: -52px;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), white);
animation: animShootingStar 6s linear infinite;
transition: all 2000ms linear;
}
@keyframes animShootingStar {
from { transform: translateY(0px) translateX(0px) rotate(-45deg); opacity: 1; height: 3px; }
to { transform: translateY(960px) translateX(960px) rotate(-45deg); opacity: 1; height: 600px; }
}
.shooting-star-2 {
z-index: 2;
width: 1px;
height: 37px;
border-bottom-left-radius: 50%;
border-bottom-right-radius: 50%;
position: absolute;
top: 0;
left: 150px;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), white);
animation: animShootingStar-2 9s linear infinite;
transition: all 2000ms linear;
}
@keyframes animShootingStar-2 {
from { transform: translateY(0px) translateX(0px) rotate(-45deg); opacity: 1; height: 3px; }
to { transform: translateY(1440px) translateX(1440px) rotate(-45deg); opacity: 1; height: 600px; }
}
/* Stars */
.star.snd { top: 75px; left: 232px; animation-delay: 1s; }
.star.trd { top: 97px; left: 75px; animation-delay: 1.4s; }
.star.fth { top: 15px; left: 150px; animation-delay: 1.8s; }
.star.fith { top: 63px; left: 165px; animation-delay: 2.2s; }
@keyframes starShine {
0% { transform: scale(0.3) rotate(0deg); opacity: 0.4; }
25% { transform: scale(1) rotate(360deg); opacity: 1; }
50% { transform: scale(0.3) rotate(720deg); opacity: 0.4; }
100% { transform: scale(0.3) rotate(0deg); opacity: 0.4; }
}
/* Trees */
.tree-1 {
position: relative;
top: 112px;
left: 37px;
width: 0;
height: 0;
z-index: 8;
border-bottom: 67px solid #0a171d;
border-left: 22px solid transparent;
border-right: 22px solid transparent;
}
.tree-1:before {
position: absolute;
bottom: -82px;
left: 50%;
margin-left: -3px;
width: 6px;
height: 22px;
z-index: 7;
content: '';
background-color: #000;
}
.tree-2 {
position: relative;
top: 0;
left: 187px;
width: 0;
height: 0;
z-index: 8;
border-bottom: 67px solid #0a171d;
border-left: 22px solid transparent;
border-right: 22px solid transparent;
}
.tree-2:before {
position: absolute;
bottom: -82px;
left: 50%;
margin-left: -3px;
width: 6px;
height: 22px;
z-index: 7;
content: '';
background-color: #000;
}
/* Fire */
.fire {
position: absolute;
z-index: 39;
width: 2px;
margin-left: -1px;
left: 50%;
bottom: 60px;
transition: all 1200ms linear;
}
.fire span {
display: block;
position: absolute;
bottom: -11px;
margin-left: -15px;
height: 0;
width: 0;
border: 22px solid #febd08; /* Main flame: yellow-orange */
border-radius: 50%;
border-top-left-radius: 0;
left: -6px;
box-shadow: 0 0 7px 3px rgba(244,110,28,0.8), 0 0 15px 7px rgba(244,110,28,0.6), 0 0 22px 11px rgba(244,110,28,0.3);
transform: scale(0.45, 0.75) rotate(45deg);
animation: brilla-fire 2.5s alternate infinite;
z-index: 9;
transition: all 1200ms linear;
}
.fire span:nth-child(2) {
left: -16px;
border: 22px solid #e63946; /* Outside flame: red */
box-shadow: 0 0 7px 3px rgba(230,57,70,0.8), 0 0 15px 7px rgba(230,57,70,0.6), 0 0 22px 11px rgba(230,57,70,0.3);
transform: scale(0.3, 0.55) rotate(15deg);
z-index: 8;
animation: brilla-fire-red 1.5s alternate infinite;
}
.fire span:nth-child(3) {
left: 3px;
border: 22px solid #e63946; /* Outside flame: red */
box-shadow: 0 0 7px 3px rgba(230,57,70,0.8), 0 0 15px 7px rgba(230,57,70,0.6), 0 0 22px 11px rgba(230,57,70,0.3);
transform: scale(0.3, 0.55) rotate(80deg);
z-index: 8;
animation: brilla-fire-red 2s alternate infinite;
}
.fire span:after {
display: block;
position: absolute;
bottom: -22px;
content: '';
margin-left: -3px;
height: 22px;
width: 9px;
background-color: rgba(244,110,28,0.7);
border-radius: 80px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
box-shadow: 0 0 15px 7px rgba(244,110,28,0.7);
left: -6px;
opacity: 0.8;
transform: rotate(-50deg);
}
.fire span:nth-child(2):after {
background-color: rgba(230,57,70,0.7); /* Match red flame */
box-shadow: 0 0 15px 7px rgba(230,57,70,0.7);
}
.fire span:nth-child(3):after {
background-color: rgba(230,57,70,0.7); /* Match red flame */
box-shadow: 0 0 15px 7px rgba(230,57,70,0.7);
}
@keyframes brilla-fire {
0%, 100% { box-shadow: 0 0 7px 3px rgba(244,110,28,0.8), 0 0 15px 7px rgba(244,110,28,0.6), 0 0 22px 11px rgba(244,110,28,0.3); }
50% { box-shadow: 0 0 10px 5px rgba(244,110,28,0.8), 0 0 21px 10px rgba(244,110,28,0.6), 0 0 31px 15px rgba(244,110,28,0.3); }
}
@keyframes brilla-fire-red {
0%, 100% { box-shadow: 0 0 7px 3px rgba(230,57,70,0.8), 0 0 15px 7px rgba(230,57,70,0.6), 0 0 22px 11px rgba(230,57,70,0.3); }
50% { box-shadow: 0 0 10px 5px rgba(230,57,70,0.8), 0 0 21px 10px rgba(230,57,70,0.6), 0 0 31px 15px rgba(230,57,70,0.3); }
}
/* Smoke */
.smoke {
position: absolute;
z-index: 40;
width: 2px;
margin-left: -1px;
left: 50%;
bottom: 79px;
opacity: 0;
transition: all 800ms linear;
}
.smoke span {
display: block;
position: absolute;
bottom: -26px;
left: 50%;
margin-left: -15px;
height: 0;
width: 0;
border: 22px solid rgba(0, 0, 0, .6);
border-radius: 16px;
border-bottom-left-radius: 0;
border-top-right-radius: 0;
left: -6px;
opacity: 0;
transform: scale(0.2, 0.2) rotate(-45deg);
}
@keyframes smokeLeft {
0% { transform: scale(0.2, 0.2) translate(0, 0) rotate(-45deg); }
10% { opacity: 1; transform: scale(0.2, 0.3) translate(0, -3px) rotate(-45deg); }
60% { opacity: 0.6; transform: scale(0.3, 0.5) translate(-7px, -60px) rotate(-45deg); }
100% { opacity: 0; transform: scale(0.4, 0.8) translate(-15px, -90px) rotate(-45deg); }
}
@keyframes smokeRight {
0% { transform: scale(0.2, 0.2) translate(0, 0) rotate(-45deg); }
10% { opacity: 1; transform: scale(0.2, 0.3) translate(0, -3px) rotate(-45deg); }
60% { opacity: 0.6; transform: scale(0.3, 0.5) translate(7px, -60px) rotate(-45deg); }
100% { opacity: 0; transform: scale(0.4, 0.8) translate(15px, -90px) rotate(-45deg); }
}
.smoke .s-0 { animation: smokeLeft 7s 0s infinite; }
.smoke .s-1 { animation: smokeRight 7s 0.7s infinite; }
.smoke .s-2 { animation: smokeLeft 7s 1.4s infinite; }
.smoke .s-3 { animation: smokeRight 7s 2.1s infinite; }
.smoke .s-4 { animation: smokeLeft 7s 2.8s infinite; }
.smoke .s-5 { animation: smokeRight 7s 3.5s infinite; }
.smoke .s-6 { animation: smokeLeft 7s 4.2s infinite; }
.smoke .s-7 { animation: smokeRight 7s 4.9s infinite; }
.smoke .s-8 { animation: smokeLeft 7s 5.6s infinite; }
.smoke .s-9 { animation: smokeRight 7s 6.3s infinite; }
/* Fire-off state (light theme) */
body:not(.fire-on) .section-center { box-shadow: 0 0 50px 5px rgba(200,200,200,.2); }
body:not(.fire-on) .smoke { opacity: 1; transition-delay: 0.8s; }
body:not(.fire-on) .fire span { bottom: -26px; transform: scale(0.15, 0.15) rotate(45deg); }

View File

@ -0,0 +1,223 @@
/* /app/static/css/fire-animation.css */
/* Removed html, body, stage styles */
.minifire-container { /* Add a wrapper for positioning/sizing */
display: flex;
align-items: center;
justify-content: center;
position: relative;
height: 130px; /* Overall height of the animation area */
overflow: hidden; /* Hide parts extending beyond the container */
background-color: #270537; /* Optional: Add a background */
border-radius: 4px;
border: 1px solid #444;
margin-top: 0.5rem; /* Space above animation */
}
.minifire-campfire {
position: relative;
/* Base size significantly reduced (original was 600px) */
width: 150px;
height: 150px;
transform-origin: bottom center;
/* Scale down slightly more if needed, adjusted positioning based on origin */
transform: scale(0.8) translateY(15px); /* Pushes it down slightly */
}
/* --- Scaled Down Logs --- */
.minifire-log {
position: absolute;
width: 60px; /* 238/4 */
height: 18px; /* 70/4 */
border-radius: 8px; /* 32/4 */
background: #781e20;
overflow: hidden;
opacity: 0.99;
transform-origin: center center;
box-shadow: 0 0 1px 0.5px rgba(0,0,0,0.15); /* Scaled shadow */
}
.minifire-log:before {
content: '';
display: block;
position: absolute;
top: 50%;
left: 9px; /* 35/4 */
width: 2px; /* 8/4 */
height: 2px; /* 8/4 */
border-radius: 8px; /* 32/4 */
background: #b35050;
transform: translate(-50%, -50%);
z-index: 3;
/* Scaled box-shadows */
box-shadow: 0 0 0 0.5px #781e20, /* 2.5/4 -> 0.6 -> 0.5 */
0 0 0 2.5px #b35050, /* 10.5/4 -> 2.6 -> 2.5 */
0 0 0 3.5px #781e20, /* 13/4 -> 3.25 -> 3.5 */
0 0 0 5.5px #b35050, /* 21/4 -> 5.25 -> 5.5 */
0 0 0 6px #781e20, /* 23.5/4 -> 5.9 -> 6 */
0 0 0 8px #b35050; /* 31.5/4 -> 7.9 -> 8 */
}
.minifire-streak {
position: absolute;
height: 1px; /* Min height */
border-radius: 5px; /* 20/4 */
background: #b35050;
}
/* Scaled streaks */
.minifire-streak:nth-child(1) { top: 3px; width: 23px; } /* 10/4, 90/4 */
.minifire-streak:nth-child(2) { top: 3px; left: 25px; width: 20px; } /* 10/4, 100/4, 80/4 */
.minifire-streak:nth-child(3) { top: 3px; left: 48px; width: 8px; } /* 10/4, 190/4, 30/4 */
.minifire-streak:nth-child(4) { top: 6px; width: 33px; } /* 22/4, 132/4 */
.minifire-streak:nth-child(5) { top: 6px; left: 36px; width: 12px; } /* 22/4, 142/4, 48/4 */
.minifire-streak:nth-child(6) { top: 6px; left: 50px; width: 7px; } /* 22/4, 200/4, 28/4 */
.minifire-streak:nth-child(7) { top: 9px; left: 19px; width: 40px; } /* 34/4, 74/4, 160/4 */
.minifire-streak:nth-child(8) { top: 12px; left: 28px; width: 10px; } /* 46/4, 110/4, 40/4 */
.minifire-streak:nth-child(9) { top: 12px; left: 43px; width: 14px; } /* 46/4, 170/4, 54/4 */
.minifire-streak:nth-child(10) { top: 15px; left: 23px; width: 28px; } /* 58/4, 90/4, 110/4 */
/* Scaled Log Positions (Relative to 150px campfire) */
.minifire-log:nth-child(1) { bottom: 25px; left: 25px; transform: rotate(150deg) scaleX(0.75); z-index: 20; } /* 100/4, 100/4 */
.minifire-log:nth-child(2) { bottom: 30px; left: 35px; transform: rotate(110deg) scaleX(0.75); z-index: 10; } /* 120/4, 140/4 */
.minifire-log:nth-child(3) { bottom: 25px; left: 17px; transform: rotate(-10deg) scaleX(0.75); } /* 98/4, 68/4 */
.minifire-log:nth-child(4) { bottom: 20px; left: 55px; transform: rotate(-120deg) scaleX(0.75); z-index: 26; } /* 80/4, 220/4 */
.minifire-log:nth-child(5) { bottom: 19px; left: 53px; transform: rotate(-30deg) scaleX(0.75); z-index: 25; } /* 75/4, 210/4 */
.minifire-log:nth-child(6) { bottom: 23px; left: 70px; transform: rotate(35deg) scaleX(0.85); z-index: 30; } /* 92/4, 280/4 */
.minifire-log:nth-child(7) { bottom: 18px; left: 75px; transform: rotate(-30deg) scaleX(0.75); z-index: 20; } /* 70/4, 300/4 */
/* --- Scaled Down Sticks --- */
.minifire-stick {
position: absolute;
width: 17px; /* 68/4 */
height: 5px; /* 20/4 */
border-radius: 3px; /* 10/4 */
box-shadow: 0 0 1px 0.5px rgba(0,0,0,0.1);
background: #781e20;
transform-origin: center center;
}
.minifire-stick:before {
content: '';
display: block;
position: absolute;
bottom: 100%;
left: 7px; /* 30/4 -> 7.5 */
width: 1.5px; /* 6/4 */
height: 5px; /* 20/4 */
background: #781e20;
border-radius: 3px; /* 10/4 */
transform: translateY(50%) rotate(32deg);
}
.minifire-stick:after {
content: '';
display: block;
position: absolute;
top: 0;
right: 0;
width: 5px; /* 20/4 */
height: 5px; /* 20/4 */
background: #b35050;
border-radius: 3px; /* 10/4 */
}
/* Scaled Stick Positions */
.minifire-stick:nth-child(1) { left: 40px; bottom: 41px; transform: rotate(-152deg) scaleX(0.8); z-index: 12; } /* 158/4, 164/4 */
.minifire-stick:nth-child(2) { left: 45px; bottom: 8px; transform: rotate(20deg) scaleX(0.9); } /* 180/4, 30/4 */
.minifire-stick:nth-child(3) { left: 100px; bottom: 10px; transform: rotate(170deg) scaleX(0.9); } /* 400/4, 38/4 */
.minifire-stick:nth-child(3):before { display: none; }
.minifire-stick:nth-child(4) { left: 93px; bottom: 38px; transform: rotate(80deg) scaleX(0.9); z-index: 20; } /* 370/4, 150/4 */
.minifire-stick:nth-child(4):before { display: none; }
/* --- Scaled Down Fire --- */
.minifire-fire .minifire-flame {
position: absolute;
transform-origin: bottom center;
opacity: 0.9;
}
/* Red Flames */
.minifire-fire__red .minifire-flame {
width: 12px; /* 48/4 */
border-radius: 12px; /* 48/4 */
background: #e20f00;
box-shadow: 0 0 20px 5px rgba(226,15,0,0.4); /* Scaled shadow */
}
/* Scaled positions/heights */
.minifire-fire__red .minifire-flame:nth-child(1) { left: 35px; height: 40px; bottom: 25px; animation: minifire-fire 2s 0.15s ease-in-out infinite alternate; } /* 138/4, 160/4, 100/4 */
.minifire-fire__red .minifire-flame:nth-child(2) { left: 47px; height: 60px; bottom: 25px; animation: minifire-fire 2s 0.35s ease-in-out infinite alternate; } /* 186/4, 240/4, 100/4 */
.minifire-fire__red .minifire-flame:nth-child(3) { left: 59px; height: 75px; bottom: 25px; animation: minifire-fire 2s 0.1s ease-in-out infinite alternate; } /* 234/4, 300/4, 100/4 */
.minifire-fire__red .minifire-flame:nth-child(4) { left: 71px; height: 90px; bottom: 25px; animation: minifire-fire 2s 0s ease-in-out infinite alternate; } /* 282/4, 360/4, 100/4 */
.minifire-fire__red .minifire-flame:nth-child(5) { left: 83px; height: 78px; bottom: 25px; animation: minifire-fire 2s 0.45s ease-in-out infinite alternate; } /* 330/4, 310/4, 100/4 */
.minifire-fire__red .minifire-flame:nth-child(6) { left: 95px; height: 58px; bottom: 25px; animation: minifire-fire 2s 0.3s ease-in-out infinite alternate; } /* 378/4, 232/4, 100/4 */
.minifire-fire__red .minifire-flame:nth-child(7) { left: 107px; height: 35px; bottom: 25px; animation: minifire-fire 2s 0.1s ease-in-out infinite alternate; } /* 426/4, 140/4, 100/4 */
/* Orange Flames */
.minifire-fire__orange .minifire-flame {
width: 12px; border-radius: 12px; background: #ff9c00;
box-shadow: 0 0 20px 5px rgba(255,156,0,0.4);
}
.minifire-fire__orange .minifire-flame:nth-child(1) { left: 35px; height: 35px; bottom: 25px; animation: minifire-fire 2s 0.05s ease-in-out infinite alternate; }
.minifire-fire__orange .minifire-flame:nth-child(2) { left: 47px; height: 53px; bottom: 25px; animation: minifire-fire 2s 0.1s ease-in-out infinite alternate; }
.minifire-fire__orange .minifire-flame:nth-child(3) { left: 59px; height: 63px; bottom: 25px; animation: minifire-fire 2s 0.35s ease-in-out infinite alternate; }
.minifire-fire__orange .minifire-flame:nth-child(4) { left: 71px; height: 75px; bottom: 25px; animation: minifire-fire 2s 0.4s ease-in-out infinite alternate; }
.minifire-fire__orange .minifire-flame:nth-child(5) { left: 83px; height: 65px; bottom: 25px; animation: minifire-fire 2s 0.5s ease-in-out infinite alternate; }
.minifire-fire__orange .minifire-flame:nth-child(6) { left: 95px; height: 51px; bottom: 25px; animation: minifire-fire 2s 0.35s ease-in-out infinite alternate; }
.minifire-fire__orange .minifire-flame:nth-child(7) { left: 107px; height: 28px; bottom: 25px; animation: minifire-fire 2s 0.1s ease-in-out infinite alternate; }
/* Yellow Flames */
.minifire-fire__yellow .minifire-flame {
width: 12px; border-radius: 12px; background: #ffeb6e;
box-shadow: 0 0 20px 5px rgba(255,235,110,0.4);
}
.minifire-fire__yellow .minifire-flame:nth-child(1) { left: 47px; height: 35px; bottom: 25px; animation: minifire-fire 2s 0.6s ease-in-out infinite alternate; }
.minifire-fire__yellow .minifire-flame:nth-child(2) { left: 59px; height: 43px; bottom: 30px; animation: minifire-fire 2s 0.4s ease-in-out infinite alternate; } /* Adjusted bottom slightly */
.minifire-fire__yellow .minifire-flame:nth-child(3) { left: 71px; height: 60px; bottom: 25px; animation: minifire-fire 2s 0.38s ease-in-out infinite alternate; }
.minifire-fire__yellow .minifire-flame:nth-child(4) { left: 83px; height: 50px; bottom: 25px; animation: minifire-fire 2s 0.22s ease-in-out infinite alternate; }
.minifire-fire__yellow .minifire-flame:nth-child(5) { left: 95px; height: 36px; bottom: 25px; animation: minifire-fire 2s 0.18s ease-in-out infinite alternate; }
/* White Flames */
.minifire-fire__white .minifire-flame {
width: 12px; border-radius: 12px; background: #fef1d9;
box-shadow: 0 0 20px 5px rgba(254,241,217,0.4);
}
.minifire-fire__white .minifire-flame:nth-child(1) { left: 39px; width: 8px; height: 25px; bottom: 25px; animation: minifire-fire 2s 0.22s ease-in-out infinite alternate; } /* Scaled width too */
.minifire-fire__white .minifire-flame:nth-child(2) { left: 45px; width: 8px; height: 30px; bottom: 25px; animation: minifire-fire 2s 0.42s ease-in-out infinite alternate; }
.minifire-fire__white .minifire-flame:nth-child(3) { left: 59px; height: 43px; bottom: 25px; animation: minifire-fire 2s 0.32s ease-in-out infinite alternate; }
.minifire-fire__white .minifire-flame:nth-child(4) { left: 71px; height: 53px; bottom: 25px; animation: minifire-fire 2s 0.8s ease-in-out infinite alternate; }
.minifire-fire__white .minifire-flame:nth-child(5) { left: 83px; height: 43px; bottom: 25px; animation: minifire-fire 2s 0.85s ease-in-out infinite alternate; }
.minifire-fire__white .minifire-flame:nth-child(6) { left: 95px; width: 8px; height: 28px; bottom: 25px; animation: minifire-fire 2s 0.64s ease-in-out infinite alternate; }
.minifire-fire__white .minifire-flame:nth-child(7) { left: 102px; width: 8px; height: 25px; bottom: 25px; animation: minifire-fire 2s 0.32s ease-in-out infinite alternate; }
/* --- Scaled Down Sparks --- */
.minifire-spark {
position: absolute;
width: 1.5px; /* 6/4 */
height: 5px; /* 20/4 */
background: #fef1d9;
border-radius: 5px; /* 18/4 -> 4.5 */
z-index: 50;
transform-origin: bottom center;
transform: scaleY(0);
}
/* Scaled spark positions/animations */
.minifire-spark:nth-child(1) { left: 40px; bottom: 53px; animation: minifire-spark 1s 0.4s linear infinite; } /* 160/4, 212/4 */
.minifire-spark:nth-child(2) { left: 45px; bottom: 60px; animation: minifire-spark 1s 1s linear infinite; } /* 180/4, 240/4 */
.minifire-spark:nth-child(3) { left: 52px; bottom: 80px; animation: minifire-spark 1s 0.8s linear infinite; } /* 208/4, 320/4 */
.minifire-spark:nth-child(4) { left: 78px; bottom: 100px; animation: minifire-spark 1s 2s linear infinite; } /* 310/4, 400/4 */
.minifire-spark:nth-child(5) { left: 90px; bottom: 95px; animation: minifire-spark 1s 0.75s linear infinite; } /* 360/4, 380/4 */
.minifire-spark:nth-child(6) { left: 98px; bottom: 80px; animation: minifire-spark 1s 0.65s linear infinite; } /* 390/4, 320/4 */
.minifire-spark:nth-child(7) { left: 100px; bottom: 70px; animation: minifire-spark 1s 1s linear infinite; } /* 400/4, 280/4 */
.minifire-spark:nth-child(8) { left: 108px; bottom: 53px; animation: minifire-spark 1s 1.4s linear infinite; } /* 430/4, 210/4 */
/* --- Keyframes (Rename to avoid conflicts) --- */
/* Use the same keyframe logic, just rename them */
@keyframes minifire-fire {
0% { transform: scaleY(1); } 28% { transform: scaleY(0.7); } 38% { transform: scaleY(0.8); } 50% { transform: scaleY(0.6); } 70% { transform: scaleY(0.95); } 82% { transform: scaleY(0.58); } 100% { transform: scaleY(1); }
}
@keyframes minifire-spark {
0%, 35% { transform: scaleY(0) translateY(0); opacity: 0; }
50% { transform: scaleY(1) translateY(0); opacity: 1; }
/* Adjusted translateY for smaller scale */
70% { transform: scaleY(1) translateY(-3px); opacity: 1; } /* 10/4 -> 2.5 -> 3 */
75% { transform: scaleY(1) translateY(-3px); opacity: 0; }
100% { transform: scaleY(0) translateY(0); opacity: 0; }
}

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

BIN
static/favicon.ico.old Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

BIN
static/img/star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

BIN
static/img/wood.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View File

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

View File

@ -0,0 +1 @@
Alias: $v3-ActCode = http://terminology.hl7.org/CodeSystem/v3-ActCode

View File

@ -0,0 +1,2 @@
Name Type File
banks-mia-leanne Instance instances/banks-mia-leanne.fsh

View File

@ -0,0 +1,16 @@
Instance: aspirin
InstanceOf: AllergyIntolerance
Usage: #example
* meta.profile = "http://hl7.org.au/fhir/core/StructureDefinition/au-core-allergyintolerance"
* clinicalStatus = $allergyintolerance-clinical#active
* clinicalStatus.text = "Active"
* verificationStatus = $allergyintolerance-verification#confirmed
* verificationStatus.text = "Confirmed"
* category = #medication
* criticality = #unable-to-assess
* code = $sct#387458008
* code.text = "Aspirin allergy"
* patient = Reference(Patient/hayes-arianne)
* recordedDate = "2024-02-10"
* recorder = Reference(PractitionerRole/specialistphysicians-swanborough-erick)
* asserter = Reference(PractitionerRole/specialistphysicians-swanborough-erick)

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,12 @@
Instance: discharge-1
InstanceOf: Encounter
Usage: #example
* meta.profile = "http://hl7.org.au/fhir/core/StructureDefinition/au-core-encounter"
* status = #finished
* class = $v3-ActCode#EMER "emergency"
* subject = Reference(Patient/ronny-irvine)
* period
* start = "2023-02-20T06:15:00+10:00"
* end = "2023-02-20T18:19:00+10:00"
* location.location = Reference(Location/murrabit-hospital)
* serviceProvider = Reference(Organization/murrabit-hospital)

View File

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

View File

@ -0,0 +1 @@
{"resourceType":"Encounter","id":"discharge-1","meta":{"profile":["http://hl7.org.au/fhir/core/StructureDefinition/au-core-encounter"]},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\"><p class=\"res-header-id\"><b>Generated Narrative: Encounter discharge-1</b></p><a name=\"discharge-1\"> </a><a name=\"hcdischarge-1\"> </a><a name=\"discharge-1-en-AU\"> </a><div style=\"display: inline-block; background-color: #d9e0e7; padding: 6px; margin: 4px; border: 1px solid #8da1b4; border-radius: 5px; line-height: 60%\"><p style=\"margin-bottom: 0px\"/><p style=\"margin-bottom: 0px\">Profile: <a href=\"StructureDefinition-au-core-encounter.html\">AU Core Encounter</a></p></div><p><b>status</b>: Finished</p><p><b>class</b>: <a href=\"http://terminology.hl7.org/6.2.0/CodeSystem-v3-ActCode.html#v3-ActCode-EMER\">ActCode EMER</a>: emergency</p><p><b>subject</b>: <a href=\"Patient-ronny-irvine.html\">Ronny Lawrence Irvine Male, DoB: ( DVA Number:\u00a0QX827261)</a></p><p><b>period</b>: 2023-02-20 06:15:00+1000 --&gt; 2023-02-20 18:19:00+1000</p><h3>Locations</h3><table class=\"grid\"><tr><td style=\"display: none\">-</td><td><b>Location</b></td></tr><tr><td style=\"display: none\">*</td><td><a href=\"Location-murrabit-hospital.html\">Location Murrabit Public Hospital</a></td></tr></table><p><b>serviceProvider</b>: <a href=\"Organization-murrabit-hospital.html\">Organization Murrabit Public Hospital</a></p></div>"},"status":"finished","class":{"system":"http://terminology.hl7.org/CodeSystem/v3-ActCode","code":"EMER","display":"emergency"},"subject":{"reference":"Patient/ronny-irvine"},"period":{"start":"2023-02-20T06:15:00+10:00","end":"2023-02-20T18:19:00+10:00"},"location":[{"location":{"reference":"Location/murrabit-hospital"}}],"serviceProvider":{"reference":"Organization/murrabit-hospital"}}

View File

@ -0,0 +1,8 @@
canonical: http://example.org
fhirVersion: 4.3.0
FSHOnly: true
applyExtensionMetadataToRoot: false
id: example
name: Example
dependencies:
hl7.fhir.us.core: 6.1.0

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"