update pathing for SD splitting, for odd names in json files.

iterate through all json files and read the contents for SD not just based on name.
This commit is contained in:
Joshua Hare 2025-05-13 20:49:02 +10:00
parent 655b186197
commit 881669e5e9

173
main.py
View File

@ -589,6 +589,153 @@ async def list_profiles(ig_id: str, version: Optional[str] = None):
logger.info(f"Found {len(profiles)} profiles in IG {ig_name} (version: {version})")
return profiles
#------------------------------------------------------------------OLD
# @app.get("/igs/{ig_id}/profiles/{profile_id}", response_model=StructureDefinition)
# async def get_profile(ig_id: str, profile_id: str, version: Optional[str] = None, include_narrative: bool = True):
# """
# Retrieve a specific StructureDefinition from an Implementation Guide (IG).
# This endpoint fetches a specific FHIR StructureDefinition (profile) from the given IG.
# It supports optional version specification and an option to strip the narrative content.
# Args:
# ig_id (str): The ID of the Implementation Guide (e.g., 'hl7.fhir.au.core' or 'hl7.fhir.au.core#1.1.0-preview').
# If the version is included in the ig_id (after '#'), it takes precedence unless overridden by the version parameter.
# profile_id (str): The ID or name of the profile to retrieve (e.g., 'AUCorePatient').
# version (str, optional): The version of the IG (e.g., '1.1.0-preview'). If not provided and ig_id contains a version,
# the version from ig_id is used; otherwise, the latest version is used.
# include_narrative (bool, optional): Whether to include the narrative (`text` element) in the StructureDefinition.
# Defaults to True. Set to False to strip the narrative, removing human-readable content.
# Returns:
# StructureDefinition: A dictionary containing the requested StructureDefinition resource.
# The response includes the `resource` field with the StructureDefinition JSON.
# If `include_narrative=False`, the `text` element will be set to null.
# Raises:
# HTTPException:
# - 400: If the IG name, version, or profile ID is invalid.
# - 404: If the IG, version, or profile is not found.
# - 500: If an error occurs during package retrieval or profile extraction.
# Example:
# - GET /igs/hl7.fhir.au.core/profiles/AUCorePatient?version=1.1.0-preview
# Returns the AUCorePatient profile with narrative included.
# - GET /igs/hl7.fhir.au.core/profiles/AUCorePatient?version=1.1.0-preview&include_narrative=false
# Returns the AUCorePatient profile with the narrative (`text` element) stripped.
# """
# logger.info(f"Retrieving profile {profile_id} for IG: {ig_id}, version: {version}, include_narrative: {include_narrative}")
# # Parse ig_id for version if it includes a '#'
# ig_name = ig_id
# if '#' in ig_id:
# parts = ig_id.split('#', 1)
# ig_name = parts[0]
# if version and parts[1] != version:
# logger.warning(f"Version specified in ig_id ({parts[1]}) conflicts with version parameter ({version}). Using version parameter.")
# else:
# version = parts[1]
# logger.info(f"Parsed ig_id: name={ig_name}, version={version}")
# # Validate ig_name
# if not ig_name or not re.match(r'^[a-zA-Z0-9\.\-_]+$', ig_name):
# logger.error(f"Invalid IG name: {ig_name}")
# raise HTTPException(status_code=400, detail="Invalid IG name. Use format like 'hl7.fhir.au.core'.")
# # Validate version if provided
# if version and not re.match(r'^[a-zA-Z0-9\.\-_]+$', version):
# logger.error(f"Invalid version: {version}")
# raise HTTPException(status_code=400, detail="Invalid version format. Use format like '1.1.0-preview'.")
# # Validate profile_id
# if not profile_id or not re.match(r'^[a-zA-Z0-9\.\-_]+$', profile_id):
# logger.error(f"Invalid profile ID: {profile_id}")
# raise HTTPException(status_code=400, detail="Invalid profile ID format.")
# # Check if profiles are cached
# cache_key = f"{ig_name}#{version if version else 'latest'}"
# if cache_key in app_config["PROFILE_CACHE"]:
# logger.info(f"Using cached profiles for IG {ig_name} (version: {version if version else 'latest'})")
# profiles = app_config["PROFILE_CACHE"][cache_key]
# for profile in profiles:
# if profile.name == profile_id or profile.url.endswith(profile_id):
# break
# else:
# logger.error(f"Profile {profile_id} not found in cached profiles for IG {ig_name} (version: {version if version else 'latest'})")
# raise HTTPException(status_code=404, detail=f"Profile '{profile_id}' not found in IG '{ig_name}' (version: {version if version else 'latest'}).")
# else:
# profiles = await list_profiles(ig_id, version)
# # Fetch package metadata
# packages = app_config["MANUAL_PACKAGE_CACHE"]
# if not packages:
# logger.error("Package cache is empty. Please refresh the cache using /refresh-cache.")
# raise HTTPException(status_code=500, detail="Package cache is empty. Please refresh the cache.")
# package = None
# for pkg in packages:
# if pkg['package_name'].lower() == ig_name.lower():
# package = pkg
# break
# if not package:
# logger.error(f"IG {ig_name} not found in cached packages.")
# raise HTTPException(status_code=404, detail=f"IG '{ig_name}' not found.")
# # Determine the version to fetch
# if version:
# target_version = None
# for ver_entry in package['all_versions']:
# if ver_entry['version'] == version:
# target_version = ver_entry['version']
# break
# if not target_version:
# logger.error(f"Version {version} not found for IG {ig_name}.")
# raise HTTPException(status_code=404, detail=f"Version '{version}' not found for IG '{ig_name}'.")
# else:
# target_version = package['latest_version']
# version = target_version
# logger.info(f"No version specified, using latest version: {target_version}")
# # Download the package
# tgz_path, error = download_package(ig_name, version, package)
# if not tgz_path:
# logger.error(f"Failed to download package for IG {ig_name} (version: {version}): {error}")
# if "404" in error:
# raise HTTPException(status_code=404, detail=f"Package for IG '{ig_name}' (version: {version}) not found.")
# raise HTTPException(status_code=500, detail=f"Failed to fetch package: {error}")
# # Extract the specific profile from the .tgz file
# profile_resource = None
# try:
# with tarfile.open(tgz_path, mode="r:gz") as tar:
# for member in tar.getmembers():
# if member.name.endswith('.json') and 'StructureDefinition' in member.name:
# f = tar.extractfile(member)
# if f:
# resource = json.load(f)
# if resource.get("resourceType") == "StructureDefinition":
# resource_name = resource.get("name", "")
# resource_url = resource.get("url", "")
# if resource_name == profile_id or resource_url.endswith(profile_id):
# profile_resource = resource
# break
# if not profile_resource:
# logger.error(f"Profile {profile_id} not found in package for IG {ig_name} (version: {version})")
# raise HTTPException(status_code=404, detail=f"Profile '{profile_id}' not found in IG '{ig_name}' (version: {version}).")
# except Exception as e:
# logger.error(f"Failed to extract profile {profile_id} from package for IG {ig_name} (version: {version}): {str(e)}")
# raise HTTPException(status_code=500, detail=f"Failed to extract profile: {str(e)}")
# # Strip narrative if requested
# if not include_narrative:
# logger.info(f"Stripping narrative from profile {profile_id}")
# if "text" in profile_resource:
# profile_resource["text"] = None
# logger.info(f"Successfully retrieved profile {profile_id} for IG {ig_name} (version: {version})")
# return StructureDefinition(resource=profile_resource)
#------------------------------------------------------------------------------end
@app.get("/igs/{ig_id}/profiles/{profile_id}", response_model=StructureDefinition)
async def get_profile(ig_id: str, profile_id: str, version: Optional[str] = None, include_narrative: bool = True):
@ -710,16 +857,26 @@ async def get_profile(ig_id: str, profile_id: str, version: Optional[str] = None
try:
with tarfile.open(tgz_path, mode="r:gz") as tar:
for member in tar.getmembers():
if member.name.endswith('.json') and 'StructureDefinition' in member.name:
if member.name.endswith('.json'): # Check all JSON files
logger.debug(f"Processing file: {member.name}")
f = tar.extractfile(member)
if f:
resource = json.load(f)
if resource.get("resourceType") == "StructureDefinition":
resource_name = resource.get("name", "")
resource_url = resource.get("url", "")
if resource_name == profile_id or resource_url.endswith(profile_id):
profile_resource = resource
break
try:
resource = json.load(f)
# Check if the resource is a StructureDefinition
if resource.get("resourceType") == "StructureDefinition":
logger.debug(f"Found StructureDefinition in file: {member.name}")
resource_name = resource.get("name", "")
resource_url = resource.get("url", "")
if resource_name == profile_id or resource_url.endswith(profile_id):
profile_resource = resource
break
else:
logger.debug(f"File {member.name} is not a StructureDefinition, resourceType: {resource.get('resourceType', 'unknown')}")
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse JSON in file {member.name}: {str(e)}")
except Exception as e:
logger.warning(f"Error processing file {member.name}: {str(e)}")
if not profile_resource:
logger.error(f"Profile {profile_id} not found in package for IG {ig_name} (version: {version})")
raise HTTPException(status_code=404, detail=f"Profile '{profile_id}' not found in IG '{ig_name}' (version: {version}).")