Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/config object #42

Merged
merged 9 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
"ghcr.io/devcontainers-contrib/features/black:2": {},
"node": "18"
},
"forwardPorts": [4200],
"forwardPorts": [
4200
],
"postCreateCommand": "pip3 install --user -r requirements.txt && npm install -g @angular/cli@16",
"customizations": {
"vscode": {
"extensions": [
"ms-python.black-formatter",
"ms-python.flake8",
"ms-python.debugpy",
"ms-python.python",
"samuelcolvin.jinjahtml",
Expand Down
22 changes: 11 additions & 11 deletions service/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
create_results_html,
gen_mapping_dict,
)
from structure_comparer.config import Config


def write_mapping_json(structured_mapping: dict, output_file: Path):
Expand All @@ -26,7 +27,8 @@ def get_args():
type=Path,
help="The project directory containing the profiles and config",
)
parser.add_argument("--html", action="store_true", help="Generate html files")
parser.add_argument("--html", action="store_true",
help="Generate html files")
parser.add_argument(
"--json",
action="store_true",
Expand All @@ -39,25 +41,23 @@ def get_args():
if __name__ == "__main__":
args = get_args()

config = json.loads((args.project_dir / "config.json").read_text())
config = Config.from_json(args.project_dir / "config.json")

# Read the manual entries
manual_entries_file = config.get("manual_entries_file", "manual_entries.yaml")
manual_entries_file = config.manual_entries_file
MANUAL_ENTRIES.read(args.project_dir / manual_entries_file)

profiles_to_compare = config["profiles_to_compare"]
data_dir = args.project_dir / config.get("data_dir", "data")
structured_mapping = compare_profiles(profiles_to_compare, data_dir)
data_dir = args.project_dir / config.data_dir
structured_mapping = compare_profiles(config.profiles_to_compare, data_dir)

if args.html:
# Create the result html files
show_remarks = config.get("show_remarks", True)
html_output_dir = args.project_dir / config.get("html_output_dir", "html")
show_remarks = config.show_remarks
html_output_dir = args.project_dir / \
config.html_output_dir
create_results_html(structured_mapping, html_output_dir, show_remarks)

if args.json:
# Generate the mapping dict and write to file
mapping_output_file = args.project_dir / config.get(
"mapping_output_file", "mapping.json"
)
mapping_output_file = args.project_dir / config.mapping_output_file
write_mapping_json(structured_mapping, mapping_output_file)
2 changes: 1 addition & 1 deletion service/src/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def get_mapping(id: str):
type: string
classification:
type: string
classifications_allowed
classifications_allowed:
type: array
items:
string
Expand Down
17 changes: 10 additions & 7 deletions service/src/structure_comparer/compare.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import logging
from collections import OrderedDict
from pathlib import Path
from profile import Profile
from typing import Dict, List

from .config import CompareConfig
from .classification import Classification
from .consts import REMARKS
from .data.comparison import Comparison, ComparisonField, ProfileField
Expand Down Expand Up @@ -35,7 +35,7 @@
logger = logging.getLogger()


def compare_profiles(profiles_to_compare: List, datapath: Path):
def compare_profiles(profiles_to_compare: List[CompareConfig], datapath: Path):
"""
Compares the presence of properties in KBV and ePA profiles.
"""
Expand All @@ -46,7 +46,7 @@ def compare_profiles(profiles_to_compare: List, datapath: Path):
return structured_results


def load_profiles(profiles_to_compare: List, datapath: Path) -> Dict[str, ProfileMap]:
def load_profiles(profiles_to_compare: List[CompareConfig], datapath: Path) -> Dict[str, ProfileMap]:
"""
Loads the FHIR structure definitions from the local JSON files.
"""
Expand All @@ -60,7 +60,8 @@ def load_profiles(profiles_to_compare: List, datapath: Path) -> Dict[str, Profil
def _compare_profiles(profile_maps: Dict[str, ProfileMap]) -> Dict[str, Comparison]:
mapping = {}
for map in profile_maps.values():
sources_key = tuple((entry.name, entry.version) for entry in map.sources)
sources_key = tuple((entry.name, entry.version)
for entry in map.sources)
target_key = (map.target.name, map.target.version)
key = str((sources_key, target_key))
mapping[key] = compare_profile(map)
Expand Down Expand Up @@ -95,7 +96,8 @@ def generate_comparison(profile_map: ProfileMap) -> Comparison:
not (field_entry := comparison.fields.get(field.name))
or field_entry.extension != field.extension
):
comparison.fields[field.name] = ComparisonField(field.name, field.id)
comparison.fields[field.name] = ComparisonField(
field.name, field.id)
comparison.fields[field.name].extension = field.extension

profile_key = source_profile.profile_key
Expand Down Expand Up @@ -180,7 +182,8 @@ def _classify_remark_field(
)

# If there is a remark in the manual entry, use it else use the default remark
remark = manual_entry.get(MANUAL_ENTRIES_REMARK, REMARKS[classification])
remark = manual_entry.get(
MANUAL_ENTRIES_REMARK, REMARKS[classification])

# If the classification needs extra information, generate the remark with the extra information
if classification in EXTRA_CLASSIFICATIONS:
Expand All @@ -201,7 +204,7 @@ def _classify_remark_field(
if classification in EXTRA_CLASSIFICATIONS:

# Cut away the common part with the parent and add the remainder to the parent's extra
extra = parent_update.extra + field.name[len(parent) :]
extra = parent_update.extra + field.name[len(parent):]
remark = REMARKS[classification].format(extra)

# Else use the parent's remark
Expand Down
99 changes: 99 additions & 0 deletions service/src/structure_comparer/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from datetime import datetime, timedelta, timezone
import json
from pathlib import Path
from typing import Dict, List


class Config:
def __init__(self) -> None:
self.manual_entries_file: str = None
self.data_dir: str = None
self.html_output_dir: str = None
self.mapping_output_file: str = None
self.profiles_to_compare: List[CompareConfig] = None
self.show_remarks: bool = None

@staticmethod
def from_json(file: str | Path) -> "Config":
file = Path(file)
dict_ = json.loads(file.read_text())
return Config.from_dict(dict_)

@staticmethod
def from_dict(dict_: Dict) -> "Config":
config = Config()
config.manual_entries_file = dict_.get(
"manual_entries_file", "manual_entries.yaml"
)
config.data_dir = dict_.get("data_dir", "data")
config.html_output_dir = dict_.get("html_output_dir", "docs")
config.mapping_output_file = dict_.get(
"mapping_output_file", "mapping.json")
config.profiles_to_compare = [
CompareConfig.from_dict(compare)
for compare in dict_.get("profiles_to_compare")
]
config.show_remarks = dict_.get("show_remarks", True)
return config


class CompareConfig:
def __init__(self) -> None:
self.id = None
self.version: str = None
self.status: str = None
self.mappings: MappingConfig = None
self.last_updated: str = None
self.status: str = None

@staticmethod
def from_dict(dict_: Dict) -> "CompareConfig":
config = CompareConfig()
config.id = dict_["id"]
config.version = dict_.get("version")

if config.version is None:
raise KeyError(
"The 'version' key is not set in the configuration of the mapping. Please set the version and try again."
)

config.status = dict_.get("status", "draft")
config.mappings = MappingConfig.from_dict(dict_.get("mappings"))
config.last_updated = dict_.get("last_updated") or (
datetime.now(timezone.utc) + timedelta(hours=2)
).strftime("%Y-%m-%d %H:%M:%S")
return config


class MappingConfig:
def __init__(self) -> None:
self.source_profiles: List[ProfileConfig] = None
self.target_profile: ProfileConfig = None

@staticmethod
def from_dict(dict_: Dict) -> "MappingConfig":
config = MappingConfig()
config.source_profiles = [
ProfileConfig.from_dict(profile) for profile in dict_.get("sourceprofiles")
]
config.target_profile = ProfileConfig.from_dict(
dict_.get("targetprofile"))
return config


class ProfileConfig:
def __init__(self) -> None:
self.file: str = None
self.version: str = None
self.simplifier_url: str = None
self.file_download_url: str = None

@staticmethod
def from_dict(dict_: Dict) -> "ProfileConfig":
config = ProfileConfig()
config.file = dict_["file"]
config.version = dict_.get("version")
config.simplifier_url = dict_.get("simplifier_url")
config.file_download_url = dict_.get("file_download_url")

return config
36 changes: 16 additions & 20 deletions service/src/structure_comparer/data/profile.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import datetime
import json
from collections import OrderedDict
from pathlib import Path
from typing import Dict, List
from uuid import uuid4

from structure_comparer.config import CompareConfig, ProfileConfig

IGNORE_ENDS = ["id", "extension", "modifierExtension"]
IGNORE_SLICES = [
"slice(url)",
Expand All @@ -27,25 +28,19 @@ def __init__(self) -> None:
self.status: str = None

@staticmethod
def from_json(profile_mapping: Dict, datapath: Path) -> "ProfileMap":
sources = profile_mapping["mappings"]["sourceprofiles"]
target = profile_mapping["mappings"]["targetprofile"]
def from_json(compare_config: CompareConfig, datapath: Path) -> "ProfileMap":
sources = compare_config.mappings.source_profiles
target = compare_config.mappings.target_profile

profiles_map = ProfileMap()
profiles_map.id = profile_mapping["id"]
profiles_map.id = compare_config.id
profiles_map.sources = [
Profile.from_dict(source, datapath) for source in sources
]
profiles_map.target = Profile.from_dict(target, datapath)
profiles_map.version = profile_mapping.get("version")
if not profiles_map.version:
raise ValueError(
"The 'version' key is not set in the configuration of the mapping. Please set the version and try again."
)
profiles_map.last_updated = profile_mapping.get("last_updated") or (
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=2)
).strftime("%Y-%m-%d %H:%M:%S")
profiles_map.status = profile_mapping.get("status", "draft")
profiles_map.version = compare_config.version
profiles_map.last_updated = compare_config.last_updated
profiles_map.status = compare_config.status

return profiles_map

Expand Down Expand Up @@ -79,20 +74,21 @@ def __repr__(self) -> str:
return str(self)

@staticmethod
def from_dict(data: Dict, datapath: Path) -> "Profile":
file_path = datapath / data["file"]
def from_dict(config: ProfileConfig, datapath: Path) -> "Profile":
file_path = datapath / config.file
if not file_path.exists():
raise FileNotFoundError(
f"The file {file_path} does not exist. Please check the file path and try again."
f"The file {
file_path} does not exist. Please check the file path and try again."
)

content = json.loads(file_path.read_text())

profile = Profile(
name=content["name"],
version=data.get("version"),
simplifier_url=data.get("simplifier_url"),
file_download_url=data.get("file_download_url"),
version=config.version,
simplifier_url=config.simplifier_url,
file_download_url=config.file_download_url,
)

extracted_elements = _extract_elements(content["snapshot"]["element"])
Expand Down
29 changes: 18 additions & 11 deletions service/src/structure_comparer/serve.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from pathlib import Path

from flask import jsonify
Expand All @@ -13,16 +12,19 @@
MANUAL_ENTRIES_CLASSIFICATION,
MANUAL_ENTRIES_EXTRA,
)
from .config import Config


def init_project(project_dir: Path):
project_obj = lambda: None
def project_obj():
return None

project_obj.dir = project_dir
project_obj.config = json.loads((project_dir / "config.json").read_text())
project_obj.data_dir = project_dir / project_obj.config.get("data_dir", "data")
project_obj.config = Config.from_json(project_dir / "config.json")
project_obj.data_dir = project_dir / project_obj.config.data_dir

# Get profiles to compare
project_obj.profiles_to_compare_list = project_obj.config["profiles_to_compare"]
project_obj.profiles_to_compare_list = project_obj.config.profiles_to_compare

# Load profiles
load_profiles(project_obj)
Expand All @@ -34,22 +36,26 @@ def init_project(project_dir: Path):


def read_manual_entries(project):
manual_entries_file = project.dir / project.config.get(
"manual_entries_file", "manual_entries.json"
)
manual_entries_file = project.dir / project.config.manual_entries_file

if not manual_entries_file.exists():
manual_entries_file.touch()

MANUAL_ENTRIES.read(manual_entries_file)


def load_profiles(project):
profile_maps = _load_profiles(project.profiles_to_compare_list, project.data_dir)
profile_maps = _load_profiles(
project.profiles_to_compare_list, project.data_dir)
project.comparisons = {
entry.id: generate_comparison(entry) for entry in profile_maps.values()
}


def get_classifications_int():
classifications = [
{"value": c.value, "remark": REMARKS[c], "instruction": INSTRUCTIONS[c]}
{"value": c.value, "remark": REMARKS[c],
"instruction": INSTRUCTIONS[c]}
for c in Classification
]
return jsonify({"classifications": classifications})
Expand Down Expand Up @@ -137,7 +143,8 @@ def post_mapping_classification_int(
# Check if action is allowed for this field
if action not in field.classifications_allowed:
raise ValueError(
f"action '{action.value}' not allowed for this field, allowed: {', '.join([field.value for field in field.classifications_allowed])}"
f"action '{action.value}' not allowed for this field, allowed: {
', '.join([field.value for field in field.classifications_allowed])}"
)

# Build the entry that should be created/updated
Expand Down
Loading