import copy
import json
from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional
from pix_framework.discovery.case_arrival import CaseArrivalModel
from pix_framework.discovery.gateway_probabilities import GatewayProbabilities
from pix_framework.discovery.resource_calendar_and_performance.fuzzy.resource_calendar import FuzzyResourceCalendar
from pix_framework.discovery.resource_model import ResourceModel
from pix_framework.io.bpmn import get_activities_ids_by_name_from_bpmn
from simod.batching.types import BatchingRule
from simod.branch_rules.types import BranchRules
from simod.data_attributes.types import CaseAttribute, GlobalAttribute, EventAttribute
from simod.extraneous_delays.types import ExtraneousDelay
from simod.prioritization.types import PrioritizationRule
from simod.utilities import get_simulation_parameters_path
# Keys for serialization
PROCESS_MODEL_KEY = "process_model"
GATEWAY_PROBABILITIES_KEY = "gateway_branching_probabilities"
CASE_ARRIVAL_DISTRIBUTION_KEY = "arrival_time_distribution"
CASE_ARRIVAL_CALENDAR_KEY = "arrival_time_calendar"
RESOURCE_PROFILES_KEY = "resource_profiles"
RESOURCE_CALENDARS_KEY = "resource_calendars"
RESOURCE_ACTIVITY_PERFORMANCE_KEY = "task_resource_distribution"
EXTRANEOUS_DELAYS_KEY = "event_distribution"
CASE_ATTRIBUTES_KEY = "case_attributes"
GLOBAL_ATTRIBUTES_KEY = "global_attributes"
EVENT_ATTRIBUTES_KEY = "event_attributes"
PRIORITIZATION_RULES_KEY = "prioritisation_rules"
BATCHING_RULES_KEY = "batch_processing"
BRANCH_RULES_KEY = "branch_rules"
[docs]
@dataclass
class BPSModel:
"""
Represents a Business Process Simulation (BPS) model containing all necessary components
to simulate a business process.
This class manages various elements such as the BPMN process model, resource configurations,
extraneous delays, case attributes, and prioritization/batching rules. It provides methods
to convert the model into a format compatible with Prosimos and handle activity ID mappings.
Attributes
----------
process_model : :class:`pathlib.Path`, optional
Path to the BPMN process model file.
gateway_probabilities : List[:class:`GatewayProbabilities`], optional
Probabilities for gateway-based process routing.
case_arrival_model : :class:`CaseArrivalModel`, optional
Model for the arrival of new cases in the simulation.
resource_model : :class:`ResourceModel`, optional
Model for the resources involved in the process, their working schedules, etc.
extraneous_delays : List[:class:`~simod.extraneous_delays.types.ExtraneousDelay`], optional
A list of delays representing extraneous waiting times before/after activities.
case_attributes : List[:class:`CaseAttribute`], optional
Case-level attributes and their update rules.
global_attributes : List[:class:`GlobalAttribute`], optional
Global attributes and their update rules.
event_attributes : List[:class:`EventAttribute`], optional
Event-level attributes and their update rules.
prioritization_rules : List[:class:`PrioritizationRule`], optional
A set of case prioritization rules for process execution.
batching_rules : List[:class:`BatchingRule`], optional
Rules defining how activities are batched together.
branch_rules : List[:class:`BranchRules`], optional
Branching rules defining conditional flow behavior in decision points.
calendar_granularity : int, optional
Granularity of the resource calendar, expressed in minutes.
Notes
-----
- `to_prosimos_format` transforms the model into a dictionary format used by Prosimos.
- `replace_activity_names_with_ids` modifies activity references to use BPMN IDs instead of names.
"""
process_model: Optional[Path] = None # A path to the model for now, in future the loaded BPMN model
gateway_probabilities: Optional[List[GatewayProbabilities]] = None
case_arrival_model: Optional[CaseArrivalModel] = None
resource_model: Optional[ResourceModel] = None
extraneous_delays: Optional[List[ExtraneousDelay]] = None
case_attributes: Optional[List[CaseAttribute]] = None
global_attributes: Optional[List[GlobalAttribute]] = None
event_attributes: Optional[List[EventAttribute]] = None
prioritization_rules: Optional[List[PrioritizationRule]] = None
batching_rules: Optional[List[BatchingRule]] = None
branch_rules: Optional[List[BranchRules]] = None
calendar_granularity: Optional[int] = None
[docs]
def deep_copy(self) -> "BPSModel":
"""
Creates a deep copy of the current BPSModel instance.
This ensures that modifying the copied instance does not affect the original.
Returns
-------
:class:`BPSModel`
A new, independent copy of the current BPSModel instance.
Notes
-----
This method uses Python's `copy.deepcopy()` to create a full recursive copy of the model.
"""
return copy.deepcopy(self)
[docs]
def replace_activity_names_with_ids(self):
"""
Replaces activity names with their corresponding IDs from the BPMN process model.
Prosimos requires activity references to be identified by their BPMN node IDs instead of
activity labels. This method updates:
- Resource associations in the resource profiles.
- Activity-resource distributions.
- Event attributes referencing activity names.
Raises
------
KeyError
If an activity name does not exist in the BPMN model.
Notes
-----
- This method modifies the model in place.
- It ensures compatibility with Prosimos by aligning activity references with BPMN IDs.
"""
# Get map activity label -> node ID
activity_label_to_id = get_activities_ids_by_name_from_bpmn(self.process_model)
# Update activity labels in resource profiles
if self.resource_model.resource_profiles is not None:
for resource_profile in self.resource_model.resource_profiles:
for resource in resource_profile.resources:
resource.assigned_tasks = [
activity_label_to_id[activity_label] for activity_label in resource.assigned_tasks
]
# Update activity labels in activity-resource performance
if self.resource_model.activity_resource_distributions is not None:
for activity_resource_distributions in self.resource_model.activity_resource_distributions:
activity_resource_distributions.activity_id = activity_label_to_id[
activity_resource_distributions.activity_id
]
# Update activity label in event attributes
if self.event_attributes is not None:
for event_attribute in self.event_attributes:
event_attribute.event_id = activity_label_to_id[event_attribute.event_id]
[docs]
def to_json(self, output_dir: Path, process_name: str) -> Path:
"""
Saves the BPS model in a Prosimos-compatible JSON format.
This method generates a structured JSON file containing all necessary simulation parameters,
ensuring that the model can be directly used by the Prosimos engine.
Parameters
----------
output_dir : :class:`pathlib.Path`
The directory where the JSON file should be saved.
process_name : str
The name of the process, used for naming the output file.
Returns
-------
:class:`pathlib.Path`
The full path to the generated JSON file.
Notes
-----
- The JSON file is created in `output_dir` with a filename based on `process_name`.
- Uses `json.dump()` to serialize the model into a structured format.
- Ensures all attributes are converted into a valid Prosimos format before writing.
"""
json_parameters_path = get_simulation_parameters_path(output_dir, process_name)
with json_parameters_path.open("w") as f:
json.dump(self.to_prosimos_format(), f)
return json_parameters_path