"""JobSpec and related configuration dataclasses — typed, validated at construction.Replaces the legacy dict-based config format. :class:`JobSpec` validates itselfin ``__post_init__`` so errors are caught before batch execution begins."""from__future__importannotationsfromdataclassesimportdataclass,fieldimportcopy
[docs]@dataclassclassHookSpec:"""Description of one extraction/pre-extraction hook script and its tasks. Attributes ---------- script_path : str Path to the Python script that processes the hook. tasks : list[dict] List of task descriptors; each dict typically contains ``result_name``, ``script_path``, and task-specific parameters. """script_path:strtasks:list[dict]=field(default_factory=list)
[docs]@dataclassclassPreparationSpec:"""Specification for the preparation phase of a modular workflow. Attributes ---------- kind : str Preparation strategy identifier. Currently ``'inp_based'`` or ``'model_generation'``. source_path : str Path to the base INP file (for ``inp_based``) or model-generation script (for ``model_generation``). params : dict Key-value parameters forwarded to the preparation strategy (e.g. placeholder replacements for ``inp_based``). """kind:strsource_path:strparams:dict=field(default_factory=dict)
[docs]@dataclassclassJobSpec:"""Single-job configuration validated at construction time. Fails fast — validation runs in ``__post_init__`` so invalid configs are rejected before any Abaqus process is launched. Attributes ---------- job_name : str Unique name for this job (also used as the working directory name). workflow : str ``'modular'`` (default, 4-phase pipeline) or ``'monolithic'`` (single-script). preparation : PreparationSpec or None Preparation spec; required when ``workflow='modular'``, ignored for monolithic. monolithic_script : str or None Path to the monolithic script; required when ``workflow='monolithic'``. monolithic_params : dict Parameters forwarded to the monolithic script as ``--key value`` args. pre_extraction : list[HookSpec] Hooks run *before* the solver (e.g. model property extraction). post_extraction : list[HookSpec] Hooks run *after* the solver (e.g. ODB result extraction). """job_name:strworkflow:str='modular'preparation:PreparationSpec|None=Nonemonolithic_script:str|None=Nonemonolithic_params:dict=field(default_factory=dict)pre_extraction:list[HookSpec]=field(default_factory=list)post_extraction:list[HookSpec]=field(default_factory=list)def__post_init__(self):"""Validate the spec after field assignment. Validation rules: * ``workflow`` must be ``'modular'`` or ``'monolithic'``. * Modular workflow requires a non-``None`` ``preparation``. * Monolithic workflow requires a non-empty ``monolithic_script``. Raises ------ ValueError If any validation rule is violated. """ifself.workflownotin('modular','monolithic'):raiseValueError(f"[{self.job_name}] unknown workflow: {self.workflow}")ifself.workflow=='modular'andself.preparationisNone:raiseValueError(f"[{self.job_name}] modular workflow requires 'preparation'")ifself.workflow=='monolithic'andnotself.monolithic_script:raiseValueError(f"[{self.job_name}] monolithic workflow requires 'monolithic_script'")
[docs]@classmethoddeffrom_dict(cls,d:dict)->"JobSpec":"""Migration bridge: construct a :class:`JobSpec` from a legacy dict. Deep-copies the input dict so the returned spec owns all of its mutable data (no shared references with the caller). Parameters ---------- d : dict Legacy configuration dict. Recognised keys: ``job_name``, ``workflow``, ``type``, ``base_inp_path``, ``model_script_path``, ``script_path``, ``params``, ``pre_extraction``, ``post_extraction``. Returns ------- JobSpec Fully validated spec. """d=copy.deepcopy(d)workflow=d.get('workflow','modular')prep=Noneifworkflow=='modular':prep=PreparationSpec(kind=d.get('type','inp_based'),source_path=d.get('base_inp_path')ord.get('model_script_path')or'',params=copy.deepcopy(d.get('params',{})))returncls(job_name=d['job_name'],workflow=workflow,preparation=prep,monolithic_script=d.get('script_path')ifworkflow=='monolithic'elseNone,monolithic_params=copy.deepcopy(d.get('params',{}))ifworkflow=='monolithic'else{},pre_extraction=[HookSpec(**h)forhind.get('pre_extraction',[])],post_extraction=[HookSpec(**h)forhind.get('post_extraction',[])],)