Source code for data_model.workflow_metadata
"""Utilities for workflow-level metadata propagation into model-parameter metadata files."""
import logging
from copy import deepcopy
from pathlib import Path
from simtools.data_model.metadata_collector import MetadataCollector
from simtools.io import ascii_handler
from simtools.utils import value_conversion
logger = logging.getLogger(__name__)
[docs]
def build_workflow_activity_metadata(
args_dict,
workflow_activity_id,
workflow_start,
workflow_end,
runtime_environment,
workflow_context,
context=None,
):
"""Build workflow activity metadata from workflow execution context.
Parameters
----------
args_dict : dict
Workflow application arguments.
workflow_activity_id : str
Workflow-level activity identifier.
workflow_start : datetime
Start time of the workflow.
workflow_end : datetime
End time of the workflow.
runtime_environment : dict or None
Runtime environment definition used for the workflow.
workflow_context : dict
Context with keys 'site' and 'instrument' for the workflow.
Returns
-------
dict
Activity block to be injected into model-parameter metadata files.
"""
metadata_args = dict(args_dict)
metadata_args["label"] = "setting_workflow"
metadata_args["activity_id"] = workflow_activity_id
metadata_args["activity_start"] = workflow_start.isoformat(timespec="seconds")
metadata_args["activity_end"] = workflow_end.isoformat(timespec="seconds")
metadata_args["runtime_environment"] = deepcopy(runtime_environment)
metadata_args["site"] = workflow_context.get("site")
metadata_args["instrument"] = workflow_context.get("instrument")
collector = MetadataCollector(metadata_args, clean_meta=False, context=context)
activity = collector.get_top_level_metadata().get("cta", {}).get("activity", {})
if runtime_environment is not None:
activity["runtime_environment"] = deepcopy(runtime_environment)
return activity
[docs]
def update_model_parameter_metadata_file(
metadata_file,
workflow_activity,
associated_activities,
):
"""Inject workflow metadata into a model-parameter metadata file.
Parameters
----------
metadata_file : str or Path
Path to the model-parameter metadata file to update.
workflow_activity : dict
Workflow activity metadata block to set as top-level activity metadata.
associated_activities : list
Ordered activities associated with workflow execution.
Returns
-------
None
Function updates file in place when it exists.
"""
metadata_path = Path(metadata_file)
if not metadata_path.exists():
logger.debug(f"Model-parameter metadata file does not exist: {metadata_path}")
return
metadata = ascii_handler.collect_data_from_file(metadata_path)
metadata = value_conversion.change_dict_keys_case(metadata, True)
cta_meta = metadata.get("cta", {})
cta_meta["activity"] = deepcopy(workflow_activity)
context = cta_meta.setdefault("context", {})
context_associated = context.get("associated_activities") or []
context["associated_activities"] = _merge_associated_activities(
context_associated,
associated_activities or [],
)
metadata["cta"] = cta_meta
ascii_handler.write_data_to_file(metadata, metadata_path)
logger.info(f"Updated workflow metadata in {metadata_path}")
def _merge_associated_activities(existing_activities, new_activities):
"""Merge associated activities preserving order and uniqueness."""
merged_activities = []
seen = set()
for activity in [*(existing_activities or []), *(new_activities or [])]:
key = (activity.get("activity_name"), activity.get("activity_id"))
if key in seen:
continue
seen.add(key)
merged_activities.append(activity)
return merged_activities