prosimos 1.2.3__tar.gz → 1.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {prosimos-1.2.3 → prosimos-1.2.4}/PKG-INFO +8 -4
  2. {prosimos-1.2.3 → prosimos-1.2.4}/README.md +5 -1
  3. {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/log_parser.py +2 -1
  4. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/batch_processing.py +6 -5
  5. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_engine.py +30 -9
  6. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_properties_parser.py +1 -1
  7. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_setup.py +8 -8
  8. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_stats_calculator.py +1 -1
  9. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/weekday_helper.py +3 -1
  10. {prosimos-1.2.3 → prosimos-1.2.4}/pyproject.toml +3 -2
  11. prosimos-1.2.3/prosimos/resource_calendar.py +0 -911
  12. {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/__init__.py +0 -0
  13. {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/emd_metric.py +0 -0
  14. {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/exceptions.py +0 -0
  15. {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/inter_arrival_cases_discovery.py +0 -0
  16. {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/log_comparison_metrics.py +0 -0
  17. {prosimos-1.2.3 → prosimos-1.2.4}/cli/__init__.py +0 -0
  18. {prosimos-1.2.3 → prosimos-1.2.4}/cli/diff_res_bpsim.py +0 -0
  19. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/__init__.py +0 -0
  20. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/batch_processing_parser.py +0 -0
  21. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/case_attributes.py +0 -0
  22. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/control_flow_manager.py +0 -0
  23. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/exceptions.py +0 -0
  24. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/execution_info.py +0 -0
  25. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/file_manager.py +0 -0
  26. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/prioritisation.py +0 -0
  27. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/prioritisation_parser.py +0 -0
  28. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/prioritisation_rules.py +0 -0
  29. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/probability_distributions.py +0 -0
  30. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/resource_profile.py +0 -0
  31. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_queues_ds.py +0 -0
  32. {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_stats.py +0 -0
@@ -1,17 +1,17 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prosimos
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary:
5
5
  Author: Iryna Halenok, Orlenys López Pintado
6
- Requires-Python: >=3.8,<3.12
6
+ Requires-Python: >=3.9,<3.12
7
7
  Classifier: Programming Language :: Python :: 3
8
- Classifier: Programming Language :: Python :: 3.8
9
8
  Classifier: Programming Language :: Python :: 3.9
10
9
  Classifier: Programming Language :: Python :: 3.10
11
10
  Classifier: Programming Language :: Python :: 3.11
12
11
  Requires-Dist: click (>=8.1.3,<9.0.0)
13
12
  Requires-Dist: numpy (>=1.24.3,<2.0.0)
14
13
  Requires-Dist: pandas (>=2.0.1,<3.0.0)
14
+ Requires-Dist: pix-framework (>=0.8.7,<0.9.0)
15
15
  Requires-Dist: pm4py (>=2.7.4,<3.0.0)
16
16
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
17
17
  Requires-Dist: pytz (>=2023.3,<2024.0)
@@ -20,6 +20,10 @@ Description-Content-Type: text/markdown
20
20
 
21
21
  # Prosimos
22
22
 
23
+ ![build](https://github.com/AutomatedProcessImprovement/prosimos/actions/workflows/python.yml/badge.svg)
24
+ ![release](https://github.com/AutomatedProcessImprovement/prosimos/actions/workflows/release-pypi.yml/badge.svg)
25
+ ![version](https://img.shields.io/github/v/tag/AutomatedProcessImprovement/prosimos)
26
+
23
27
  Prosimos is a Business Process Simulation Engine that supports differentiated resources.
24
28
  Prosimos considers resource pools formed by a set of resources (process participants) with different profiles.
25
29
  For example, each resource has individual calendars, costs, and a differentiated performance to execute the process activities.
@@ -27,7 +31,7 @@ Besides, pools can share resources, i.e., a resource may play different roles in
27
31
 
28
32
  ## Requirements
29
33
 
30
- - Python 3.8+
34
+ - Python 3.9+
31
35
  - Poetry 1.4.2
32
36
  - For dependencies, please, check `pyproject.toml`
33
37
 
@@ -1,5 +1,9 @@
1
1
  # Prosimos
2
2
 
3
+ ![build](https://github.com/AutomatedProcessImprovement/prosimos/actions/workflows/python.yml/badge.svg)
4
+ ![release](https://github.com/AutomatedProcessImprovement/prosimos/actions/workflows/release-pypi.yml/badge.svg)
5
+ ![version](https://img.shields.io/github/v/tag/AutomatedProcessImprovement/prosimos)
6
+
3
7
  Prosimos is a Business Process Simulation Engine that supports differentiated resources.
4
8
  Prosimos considers resource pools formed by a set of resources (process participants) with different profiles.
5
9
  For example, each resource has individual calendars, costs, and a differentiated performance to execute the process activities.
@@ -7,7 +11,7 @@ Besides, pools can share resources, i.e., a resource may play different roles in
7
11
 
8
12
  ## Requirements
9
13
 
10
- - Python 3.8+
14
+ - Python 3.9+
11
15
  - Poetry 1.4.2
12
16
  - For dependencies, please, check `pyproject.toml`
13
17
 
@@ -6,6 +6,8 @@ from datetime import datetime
6
6
  import numpy as np
7
7
  import pandas as pd
8
8
  import pytz
9
+ from pix_framework.calendar.resource_calendar import RCalendar
10
+ from pix_framework.discovery.calendar_factory import CalendarFactory
9
11
 
10
12
  from bpdfr_discovery.exceptions import InvalidInputDiscoveryParameters
11
13
  from prosimos.control_flow_manager import BPMN, BPMNGraph
@@ -13,7 +15,6 @@ from prosimos.exceptions import InvalidBpmnModelException, InvalidLogFileExcepti
13
15
  from prosimos.execution_info import Trace, TaskEvent
14
16
  from prosimos.file_manager import FileManager
15
17
  from prosimos.probability_distributions import best_fit_distribution
16
- from prosimos.resource_calendar import RCalendar, CalendarFactory
17
18
  from prosimos.simulation_properties_parser import parse_simulation_model
18
19
 
19
20
  print_info = False
@@ -1,12 +1,13 @@
1
- from enum import Enum
2
1
  import operator as operator
3
- from random import choices
4
2
  import sys
5
- from typing import List
6
3
  from datetime import date, datetime, time, timedelta
7
- from prosimos.exceptions import InvalidRuleDefinition
4
+ from enum import Enum
5
+ from random import choices
6
+ from typing import List
8
7
 
9
- from prosimos.resource_calendar import str_week_days
8
+ from pix_framework.calendar.resource_calendar import str_week_days
9
+
10
+ from prosimos.exceptions import InvalidRuleDefinition
10
11
  from prosimos.weekday_helper import CustomDatetimeAndSeconds, get_nearest_abs_day, get_nearest_past_day
11
12
 
12
13
 
@@ -1,21 +1,19 @@
1
1
  import csv
2
+ import datetime
3
+ from datetime import timedelta
2
4
  from typing import List
3
5
 
4
6
  import pytz
5
- import datetime
6
- from datetime import timedelta
7
+ from dateutil import parser
8
+
7
9
  from prosimos.control_flow_manager import (
8
10
  BPMN,
9
11
  CustomDatetimeAndSeconds,
10
12
  )
11
-
12
- from prosimos.file_manager import FileManager
13
13
  from prosimos.execution_info import Trace, TaskEvent, EnabledEvent
14
+ from prosimos.file_manager import FileManager
14
15
  from prosimos.prioritisation import CasePrioritisation
15
- from prosimos.resource_calendar import get_string_from_datetime
16
- from prosimos.resource_calendar import parse_datetime
17
16
  from prosimos.simulation_queues_ds import (
18
- PriorityQueue,
19
17
  DiffResourceQueue,
20
18
  EventQueue,
21
19
  )
@@ -724,7 +722,7 @@ def run_simpy_simulation(diffsim_info, stat_fwriter, log_fwriter):
724
722
  def verify_miliseconds(array):
725
723
  """
726
724
  In case of datetime.microsecond = 0, standard converter does not print microseconds
727
- So we force the convertation, so that the datetime format is the same for every datetime in the final file
725
+ So we force the conversion, so that the datetime format is the same for every datetime in the final file
728
726
  Indexes correspond to the next values:
729
727
  2 - enabled_datetime
730
728
  3 - start_datetime
@@ -732,6 +730,29 @@ def verify_miliseconds(array):
732
730
  """
733
731
  for i in range(2, 5):
734
732
  if array[i].microsecond == 0:
735
- array[i] = get_string_from_datetime(array[i])
733
+ array[i] = _get_string_from_datetime(array[i])
736
734
 
737
735
  return array
736
+
737
+
738
+ def _get_string_from_datetime(datetime):
739
+ datetime_without_colon = datetime.strftime("%Y-%m-%d %H:%M:%S.%f%z")
740
+ return "{0}:{1}".format(
741
+ datetime_without_colon[:-2],
742
+ datetime_without_colon[-2:]
743
+ )
744
+
745
+
746
+ def parse_datetime(time, has_date):
747
+ time_formats = ['%H:%M:%S.%f', '%H:%M', '%I:%M%p', '%H:%M:%S', '%I:%M:%S%p'] if not has_date \
748
+ else ['%Y-%m-%dT%H:%M:%S.%f%z', '%b %d %Y %I:%M%p', '%b %d %Y at %I:%M%p',
749
+ '%B %d, %Y, %H:%M:%S', '%a,%d/%m/%y,%I:%M%p', '%a, %d %B, %Y', '%Y-%m-%dT%H:%M:%SZ']
750
+ try:
751
+ return parser.parse(time)
752
+ except:
753
+ for time_format in time_formats:
754
+ try:
755
+ return datetime.datetime.strptime(time, time_format)
756
+ except ValueError:
757
+ pass
758
+ raise ValueError
@@ -2,6 +2,7 @@ import json
2
2
  import xml.etree.ElementTree as ET
3
3
 
4
4
  from numpy import exp, log, sqrt
5
+ from pix_framework.calendar.resource_calendar import RCalendar
5
6
 
6
7
  from prosimos.batch_processing_parser import BatchProcessingParser
7
8
  from prosimos.case_attributes import AllCaseAttributes, CaseAttribute
@@ -14,7 +15,6 @@ from prosimos.control_flow_manager import (
14
15
  from prosimos.prioritisation import AllPriorityRules
15
16
  from prosimos.prioritisation_parser import PrioritisationParser
16
17
  from prosimos.probability_distributions import *
17
- from prosimos.resource_calendar import RCalendar
18
18
  from prosimos.resource_profile import PoolInfo, ResourceProfile
19
19
 
20
20
  bpmn_schema_url = "http://www.omg.org/spec/BPMN/20100524/MODEL"
@@ -1,13 +1,14 @@
1
- import pytz
2
1
  import datetime
3
- from datetime import timedelta
4
2
  import ntpath
5
- from prosimos.batch_processing import BatchConfigPerTask
3
+ from datetime import timedelta
6
4
  from typing import Optional
7
5
 
6
+ import pytz
7
+ from pix_framework.calendar.resource_calendar import RCalendar
8
+
9
+ from prosimos.batch_processing import BatchConfigPerTask
8
10
  from prosimos.control_flow_manager import ProcessState, ElementInfo, BPMN
9
11
  from prosimos.probability_distributions import generate_number_from
10
- from prosimos.resource_calendar import RCalendar
11
12
  from prosimos.simulation_properties_parser import parse_simulation_model, parse_json_sim_parameters
12
13
 
13
14
 
@@ -27,7 +28,7 @@ class SimDiffSetup:
27
28
  self.arrival_calendar = self.find_arrival_calendar()
28
29
 
29
30
  self.is_event_added_to_log = is_event_added_to_log
30
- self.total_num_cases = total_cases # how many process cases should be simulated
31
+ self.total_num_cases = total_cases # how many process cases should be simulated
31
32
 
32
33
  def verify_simulation_input(self):
33
34
  for e_id in self.bpmn_graph.element_info:
@@ -89,8 +90,8 @@ class SimDiffSetup:
89
90
 
90
91
  def ideal_task_duration(self, task_id, resource_id, num_tasks_in_batch):
91
92
  val = generate_number_from(self.task_resource[task_id][resource_id]['distribution_name'],
92
- self.task_resource[task_id][resource_id]['distribution_params'])
93
-
93
+ self.task_resource[task_id][resource_id]['distribution_params'])
94
+
94
95
  if num_tasks_in_batch == 0:
95
96
  # task executed NOT in batch
96
97
  return val
@@ -102,7 +103,6 @@ class SimDiffSetup:
102
103
 
103
104
  return curr_batch_info.calculate_ideal_duration(val, num_tasks_in_batch)
104
105
 
105
-
106
106
  def real_task_duration(self, task_duration, resource_id, enabled_at):
107
107
  return self.calendars_map[self.resources_map[resource_id].calendar_id].find_idle_time(enabled_at, task_duration)
108
108
 
@@ -3,7 +3,7 @@ import datetime
3
3
  import pytz
4
4
  from prosimos.control_flow_manager import BPMN
5
5
 
6
- from prosimos.resource_calendar import Interval
6
+ from pix_framework.calendar.resource_calendar import Interval
7
7
 
8
8
  from prosimos.execution_info import TaskEvent, Trace
9
9
  from prosimos.simulation_setup import SimDiffSetup
@@ -1,5 +1,7 @@
1
1
  from datetime import timedelta
2
- from prosimos.resource_calendar import str_week_days
2
+
3
+ from pix_framework.calendar.resource_calendar import str_week_days
4
+
3
5
 
4
6
  class CustomDatetimeAndSeconds:
5
7
  def __init__(self, seconds_from_start, datetime):
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "prosimos"
3
- version = "1.2.3"
3
+ version = "1.2.4"
4
4
  description = ""
5
5
  authors = ["Iryna Halenok, Orlenys López Pintado"]
6
6
  readme = "README.md"
@@ -11,7 +11,7 @@ packages = [
11
11
  ]
12
12
 
13
13
  [tool.poetry.dependencies]
14
- python = ">=3.8,<3.12"
14
+ python = ">=3.9,<3.12"
15
15
  click = "^8.1.3"
16
16
  numpy = "^1.24.3"
17
17
  pandas = "^2.0.1"
@@ -19,6 +19,7 @@ python-dateutil = "^2.8.2"
19
19
  pytz = "^2023.3"
20
20
  scipy = "^1.10.1"
21
21
  pm4py = "^2.7.4"
22
+ pix-framework = "^0.8.7"
22
23
 
23
24
  [tool.poetry.dev-dependencies]
24
25
  pytest = "^7.3.1"
@@ -1,911 +0,0 @@
1
- import datetime
2
- import math
3
- from datetime import timedelta
4
- from dateutil import parser
5
-
6
- import pytz
7
-
8
- str_week_days = {"MONDAY": 0, "TUESDAY": 1, "WEDNESDAY": 2, "THURSDAY": 3, "FRIDAY": 4, "SATURDAY": 5, "SUNDAY": 6}
9
- int_week_days = {0: "MONDAY", 1: "TUESDAY", 2: "WEDNESDAY", 3: "THURSDAY", 4: "FRIDAY", 5: "SATURDAY", 6: "SUNDAY"}
10
-
11
- convertion_table = {'WEEKS': 604800,
12
- 'DAYS': 86400,
13
- 'HOURS': 3600,
14
- 'MINUTES': 60,
15
- 'SECONDS': 1}
16
-
17
-
18
- class CalendarItem:
19
- def __init__(self, from_day, to_day, begin_time, end_time):
20
- self.from_day = from_day
21
- self.to_day = to_day
22
- self.begin_time = begin_time
23
- self.end_time = end_time
24
-
25
-
26
- class Interval:
27
- def __init__(self, start, end):
28
- self.start = start
29
- if end < start and end.hour == 0 and end.minute == 0:
30
- end.replace(hour=23, minute=59, second=59, microsecond=999)
31
- self.end = end
32
- self.duration = (end - start).total_seconds()
33
-
34
- def merge_interval(self, n_interval):
35
- self.start = min(n_interval.start, self.start)
36
- self.end = max(n_interval.end, self.end)
37
- self.duration = (self.end - self.start).total_seconds()
38
-
39
- def is_before(self, c_date):
40
- return self.end <= c_date
41
-
42
- def contains(self, c_date):
43
- return self.start < c_date < self.end
44
-
45
- def contains_inclusive(self, c_date):
46
- return self.start <= c_date <= self.end
47
-
48
- def is_after(self, c_date):
49
- return c_date <= self.start
50
-
51
- def intersection(self, interval):
52
- if interval is None:
53
- return None
54
- [first_i, second_i] = [self, interval] if self.start <= interval.start else [interval, self]
55
- if second_i.start < first_i.end:
56
- return Interval(max(first_i.start, second_i.start), min(first_i.end, second_i.end))
57
- return None
58
-
59
-
60
- class CalendarIterator:
61
- def __init__(self, start_date: datetime, calendar_info):
62
- self.start_date = start_date
63
-
64
- self.calendar = calendar_info
65
-
66
- self.c_day = start_date.date().weekday()
67
-
68
- c_date = datetime.datetime.combine(calendar_info.default_date, start_date.time())
69
- c_interval = calendar_info.work_intervals[self.c_day][0]
70
- self.c_index = -1
71
- while c_interval.end < c_date and self.c_index < len(calendar_info.work_intervals[self.c_day]) - 1:
72
- self.c_index += 1
73
- c_interval = calendar_info.work_intervals[self.c_day][self.c_index]
74
-
75
- self.c_interval = Interval(self.start_date,
76
- self.start_date + timedelta(seconds=(c_interval.end - c_date).total_seconds()))
77
-
78
- def next_working_interval(self):
79
- res_interval = self.c_interval
80
- day_intervals = self.calendar.work_intervals[self.c_day]
81
- p_duration = 0
82
-
83
- self.c_index += 1
84
- if self.c_index >= len(day_intervals):
85
- p_duration += 86400 - (day_intervals[self.c_index - 1].end - self.calendar.new_day).total_seconds()
86
- while True:
87
- self.c_day = (self.c_day + 1) % 7
88
- day_intervals = self.calendar.work_intervals[self.c_day]
89
- if len(day_intervals) > 0:
90
- p_duration += (day_intervals[0].start - self.calendar.new_day).total_seconds()
91
- break
92
- else:
93
- p_duration += 86400
94
- self.c_index = 0
95
- elif self.c_index > 0:
96
- p_duration += (day_intervals[self.c_index].start - day_intervals[self.c_index - 1].end).total_seconds()
97
- self.c_interval = Interval(res_interval.end + timedelta(seconds=p_duration),
98
- res_interval.end + timedelta(
99
- seconds=p_duration + day_intervals[self.c_index].duration))
100
- return res_interval
101
-
102
-
103
- class IntervalPoint:
104
- def __init__(self, date_time, week_day, index, to_start_dist, to_end_dist):
105
- self.date_time = date_time
106
- self.week_day = week_day
107
- self.index = index
108
- self.to_start_dist = to_start_dist
109
- self.to_end_dist = to_end_dist
110
-
111
- def in_same_interval(self, another_point):
112
- return self.week_day == another_point.week_day and self.index == another_point.index
113
-
114
-
115
- class RCalendar:
116
- def __init__(self, calendar_id):
117
- self.calendar_id = calendar_id
118
- self.default_date = None
119
- self.new_day = None
120
- self.work_intervals = dict()
121
- self.cumulative_work_durations = dict()
122
- self.work_rest_count = dict()
123
- self.total_weekly_work = 0
124
- self.total_weekly_rest = to_seconds(1, 'WEEKS')
125
- for i in range(0, 7):
126
- self.work_intervals[i] = list()
127
- self.cumulative_work_durations[i] = list()
128
- self.work_rest_count[i] = [0, to_seconds(1, 'DAYS')]
129
-
130
- def print_calendar_info(self):
131
- print('Calendar ID: %s' % self.calendar_id)
132
- print('Total Weekly Work: %.2f Hours' % (self.total_weekly_work / 3600))
133
- for i in range(0, 7):
134
- if len(self.work_intervals[i]) > 0:
135
- print(int_week_days[i])
136
- for interval in self.work_intervals[i]:
137
- print(' from %02d:%02d - to %02d:%02d' % (interval.start.hour, interval.start.minute,
138
- interval.end.hour, interval.end.minute))
139
- print('-----------------------------------------------------------')
140
-
141
- def to_json(self):
142
- items = []
143
- for i in range(0, 7):
144
- if len(self.work_intervals[i]) > 0:
145
- for interval in self.work_intervals[i]:
146
- items.append({
147
- 'from': int_week_days[i],
148
- 'to': int_week_days[i],
149
- "beginTime": str(interval.start.time()),
150
- "endTime": str(interval.end.time())
151
- })
152
- return items
153
-
154
- def is_working_datetime(self, date_time):
155
- c_day = date_time.date().weekday()
156
- c_date = datetime.datetime.combine(self.default_date, date_time.time())
157
- i_index = 0
158
- for interval in self.work_intervals[c_day]:
159
- if interval.contains_inclusive(c_date):
160
- return True, IntervalPoint(date_time, i_index, c_day, (c_date - interval.start).total_seconds(),
161
- (interval.end - c_date).total_seconds())
162
- i_index += 1
163
- return False, None
164
-
165
- def combine_calendar(self, new_calendar):
166
- for i in range(0, 7):
167
- if len(new_calendar.work_intervals[i]) > 0:
168
- for interval in new_calendar.work_intervals[i]:
169
- self.add_calendar_item(int_week_days[i], int_week_days[i],
170
- str(interval.start.time()), str(interval.end.time()))
171
-
172
- def add_calendar_item(self, from_day, to_day, begin_time, end_time):
173
- if from_day.upper() in str_week_days and to_day.upper() in str_week_days:
174
- try:
175
- t_interval = Interval(parse_datetime(begin_time, False), parse_datetime(end_time, False))
176
- if self.default_date is None:
177
- self.default_date = t_interval.start.date()
178
- self.new_day = datetime.datetime.combine(self.default_date, datetime.time())
179
- d_s = str_week_days[from_day]
180
- d_e = str_week_days[to_day]
181
- while True:
182
- self._add_interval(d_s % 7, t_interval)
183
- if d_s % 7 == d_e:
184
- break
185
- d_s += 1
186
- except ValueError:
187
- return
188
-
189
- def compute_cumulative_durations(self):
190
- for w_day in self.work_intervals:
191
- cumulative = 0
192
- for interval in self.work_intervals[w_day]:
193
- cumulative += interval.duration
194
- self.cumulative_work_durations[w_day].append(cumulative)
195
-
196
- def _add_interval(self, w_day, interval):
197
- i = 0
198
- for to_merge in self.work_intervals[w_day]:
199
- if to_merge.end < interval.start:
200
- i += 1
201
- continue
202
- if interval.end < to_merge.start:
203
- break
204
- merged_duration = to_merge.duration
205
- to_merge.merge_interval(interval)
206
- merged_duration = to_merge.duration - merged_duration
207
- i += 1
208
- while i < len(self.work_intervals[w_day]):
209
- next_i = self.work_intervals[w_day][i]
210
- if to_merge.end < next_i.start:
211
- break
212
- if next_i.end <= to_merge.end:
213
- merged_duration -= next_i.duration
214
- elif next_i.start <= to_merge.end:
215
- merged_duration -= (to_merge.end - next_i.start).total_seconds()
216
- to_merge.merge_interval(next_i)
217
- del self.work_intervals[w_day][i]
218
- if merged_duration > 0:
219
- self._update_calendar_durations(w_day, merged_duration)
220
- return
221
- self.work_intervals[w_day].insert(i, interval)
222
- self._update_calendar_durations(w_day, interval.duration)
223
-
224
- def _update_calendar_durations(self, w_day, duration):
225
- self.work_rest_count[w_day][0] += duration
226
- self.work_rest_count[w_day][1] -= duration
227
- self.total_weekly_work += duration
228
- self.total_weekly_rest -= duration
229
-
230
- def remove_idle_times(self, from_date, to_date, out_intervals: list):
231
- calendar_it = CalendarIterator(from_date, self)
232
- while True:
233
- c_interval = calendar_it.next_working_interval()
234
- if c_interval.end < to_date:
235
- out_intervals.append(c_interval)
236
- else:
237
- if c_interval.start <= to_date <= c_interval.end:
238
- out_intervals.append(Interval(c_interval.start, to_date))
239
- break
240
-
241
- def find_idle_time(self, requested_date, duration):
242
- if duration == 0:
243
- return 0
244
- real_duration = 0
245
- pending_duration = duration
246
- if duration > self.total_weekly_work:
247
- real_duration += to_seconds(int(duration / self.total_weekly_work), 'WEEKS')
248
- pending_duration %= self.total_weekly_work
249
- # Addressing the first day as an special case
250
- c_day = requested_date.date().weekday()
251
- c_date = datetime.datetime.combine(self.default_date, requested_date.time())
252
-
253
- worked_time, total_time = self._find_time_starting(pending_duration, c_day, c_date)
254
- if worked_time > total_time and worked_time - total_time < 0.001:
255
- total_time = worked_time
256
- pending_duration -= worked_time
257
- real_duration += total_time
258
- c_date = self.new_day
259
- while pending_duration > 0:
260
- c_day += 1
261
- r_d = c_day % 7
262
- if pending_duration > self.work_rest_count[r_d][0]:
263
- pending_duration -= self.work_rest_count[r_d][0]
264
- real_duration += 86400
265
- else:
266
- real_duration += self._find_time_completion(pending_duration, self.work_rest_count[r_d][0], r_d, c_date)
267
- break
268
- return real_duration
269
-
270
- def next_available_time(self, requested_date):
271
- """
272
- Validates whether the 'requested_date' is located in the arrival time calendar.
273
- Valid = complies with the provided time periods of the arrival calendar.
274
- If the 'requested_date' is valid, 0 is being returned (no waiting time).
275
- If not, the number of seconds we need to wait till the time the datetime will
276
- become eligible and comply with the time periods of the calendar.
277
- """
278
- c_day = requested_date.date().weekday()
279
- c_date = datetime.datetime.combine(self.default_date, requested_date.time())
280
-
281
- for interval in self.work_intervals[c_day]:
282
- if interval.end == c_day:
283
- continue
284
- if interval.is_after(c_date):
285
- return (interval.start - c_date).total_seconds()
286
- if interval.contains(c_date):
287
- return 0
288
- duration = 86400 - (c_date - self.new_day).total_seconds()
289
- for i in range(c_day + 1, c_day + 8):
290
- r_day = i % 7
291
- if self.work_rest_count[r_day][0] > 0:
292
- return duration + (self.work_intervals[r_day][0].start - self.new_day).total_seconds()
293
- duration += 86400
294
- return duration
295
-
296
- def find_working_time(self, start_date, end_date):
297
- pending_duration = (end_date - start_date).total_seconds()
298
- worked_hours = 0
299
-
300
- c_day = start_date.date().weekday()
301
- c_date = datetime.datetime.combine(self.default_date, start_date.time())
302
-
303
- to_complete_day = 86400 - (c_date - self.new_day).total_seconds()
304
- available_work = self._calculate_available_duration(c_day, c_date)
305
-
306
- previous_date = c_date
307
- while pending_duration > to_complete_day:
308
- pending_duration -= to_complete_day
309
- worked_hours += available_work
310
- c_day = (c_day + 1) % 7
311
- available_work = self.work_rest_count[c_day][0]
312
- to_complete_day = 86400
313
- previous_date = self.new_day
314
-
315
- for interval in self.work_intervals[c_day]:
316
- if interval.is_before(previous_date):
317
- continue
318
- interval_duration = interval.duration
319
- if interval.contains(previous_date):
320
- interval_duration -= (previous_date - interval.start).total_seconds()
321
- else:
322
- pending_duration -= (interval.start - previous_date).total_seconds()
323
- if pending_duration >= interval_duration:
324
- worked_hours += interval_duration
325
- elif pending_duration > 0:
326
- worked_hours += pending_duration
327
- pending_duration -= interval_duration
328
- if pending_duration <= 0:
329
- break
330
- previous_date = interval.end
331
- return worked_hours
332
-
333
- def _find_time_starting(self, pending_duration, c_day, from_date):
334
- available_duration = self._calculate_available_duration(c_day, from_date)
335
- if available_duration <= pending_duration:
336
- return available_duration, 86400 - (from_date - self.new_day).total_seconds()
337
- else:
338
- return pending_duration, self._find_time_completion(pending_duration, available_duration, c_day, from_date)
339
-
340
- def _calculate_available_duration(self, c_day, from_date):
341
- i = -1
342
- passed_duration = 0
343
- for t_interval in self.work_intervals[c_day]:
344
- i += 1
345
- if t_interval.is_before(from_date):
346
- passed_duration += t_interval.duration
347
- continue
348
- if t_interval.is_after(from_date):
349
- break
350
- if t_interval.contains(from_date):
351
- passed_duration += (from_date - self.work_intervals[c_day][i].start).total_seconds()
352
- break
353
-
354
- return self.work_rest_count[c_day][0] - passed_duration
355
-
356
- def _find_time_completion(self, pending_duration, total_duration, c_day, from_datetime):
357
- i = len(self.work_intervals[c_day]) - 1
358
- while total_duration > pending_duration:
359
- total_duration -= self.work_intervals[c_day][i].duration
360
- i -= 1
361
- if total_duration < pending_duration:
362
- to_datetime = self.work_intervals[c_day][i + 1].start + timedelta(
363
- seconds=(pending_duration - total_duration))
364
- return (to_datetime - from_datetime).total_seconds()
365
- else:
366
- return (self.work_intervals[c_day][i].end - from_datetime).total_seconds()
367
-
368
-
369
- def parse_datetime(time, has_date):
370
- time_formats = ['%H:%M:%S.%f', '%H:%M', '%I:%M%p', '%H:%M:%S', '%I:%M:%S%p'] if not has_date \
371
- else ['%Y-%m-%dT%H:%M:%S.%f%z', '%b %d %Y %I:%M%p', '%b %d %Y at %I:%M%p',
372
- '%B %d, %Y, %H:%M:%S', '%a,%d/%m/%y,%I:%M%p', '%a, %d %B, %Y', '%Y-%m-%dT%H:%M:%SZ']
373
- try:
374
- return parser.parse(time)
375
- except:
376
- for time_format in time_formats:
377
- try:
378
- return datetime.datetime.strptime(time, time_format)
379
- except ValueError:
380
- pass
381
- raise ValueError
382
-
383
- def get_string_from_datetime(datetime):
384
- datetime_without_colon = datetime.strftime("%Y-%m-%d %H:%M:%S.%f%z")
385
- return "{0}:{1}".format(
386
- datetime_without_colon[:-2],
387
- datetime_without_colon[-2:]
388
- )
389
-
390
- def to_seconds(value, from_unit):
391
- u_from = from_unit.upper()
392
- return value * convertion_table[u_from] if u_from in convertion_table else value
393
-
394
-
395
- def seconds_from_day_beginning(from_date):
396
- return from_date.hour * 3600 + from_date.minute * 60 + from_date.second
397
-
398
-
399
- def convert_time_unit_from_to(value, from_unit, to_unit):
400
- u_from = from_unit.upper()
401
- u_to = to_unit.upper()
402
- return value * convertion_table[u_from] / convertion_table[
403
- u_to] if u_from in convertion_table and u_to in convertion_table else value
404
-
405
-
406
- def update_montly_calendars(calendar_tree, date_time, minutes_in_granule):
407
- month = date_time.month
408
- week_day = date_time.weekday()
409
- minute_granule = (date_time.hour * 60 + date_time.minute) / minutes_in_granule
410
-
411
- if month not in calendar_tree:
412
- calendar_tree[month] = dict()
413
- if week_day not in calendar_tree[month]:
414
- calendar_tree[month][week_day] = dict()
415
- if minute_granule not in calendar_tree[month][week_day]:
416
- calendar_tree[month][week_day][minute_granule] = 0
417
- calendar_tree[month][week_day][minute_granule] += 1
418
-
419
-
420
- def update_weekly_calendar(r_calendar, date_time, minutes_x_granule=15):
421
- week_day = int_week_days[date_time.weekday()]
422
- from_minute = (date_time.minute // minutes_x_granule) * minutes_x_granule
423
- to_minute = from_minute + minutes_x_granule
424
- if to_minute >= 60:
425
- if date_time.hour == 23:
426
- r_calendar.add_calendar_item(week_day, week_day, "%d:%d:%d" % (date_time.hour, from_minute, 0),
427
- "23:59:59.999")
428
- else:
429
- r_calendar.add_calendar_item(week_day, week_day, "%d:%d:%d" % (date_time.hour, from_minute, 0),
430
- "%d:%d:%d" % (date_time.hour + 1, 0, 0))
431
- else:
432
- r_calendar.add_calendar_item(week_day, week_day, "%d:%d:%d" % (date_time.hour, from_minute, 0),
433
- "%d:%d:%d" % (date_time.hour, to_minute, 0))
434
-
435
-
436
- class CalendarNode:
437
- def __init__(self):
438
- self.frequency = 0
439
- self.children_nodes = dict()
440
-
441
-
442
- class CalendarTree:
443
- def __init__(self):
444
- self.value_to_children = dict()
445
-
446
- def insert_date(self, date_prefix: list):
447
- c_level = self.value_to_children
448
- for c_value in date_prefix:
449
- if c_value not in c_level:
450
- c_level[c_value] = CalendarNode()
451
- c_level[c_value].frequency += 1
452
- c_level = c_level[c_value].children_nodes
453
-
454
-
455
- class CalendarKPIInfoFactory:
456
- def __init__(self, minutes_x_granule=15):
457
- self.minutes_x_granule = minutes_x_granule
458
- self.total_granules = 1440 % self.minutes_x_granule
459
-
460
- self.g_discarded = dict()
461
-
462
- # Fields to calculate Confidence and Support
463
- self.res_active_weekdays = dict()
464
- self.res_active_granules_weekdays = dict()
465
- self.res_enabled_task_granules = None
466
- self.res_granules_frequency = dict()
467
- self.active_res_task_weekdays = dict()
468
- self.active_res_task_weekdays_granules = dict()
469
- self.res_task_weekdays_granules_freq = dict()
470
- self.shared_task_granules = dict()
471
- self.is_joint_resource = dict()
472
- self.joint_to_task = dict()
473
- self.observed_weekdays = dict()
474
-
475
- # Fields to compute resource frequencies (needed for participation ratio)
476
- self.resource_freq = dict()
477
- self.resource_task_freq = dict()
478
- self.max_resource_freq = 0
479
- self.max_resource_task_freq = dict()
480
-
481
- self.task_events_count = dict()
482
- self.task_events_in_calendar = dict()
483
- self.total_events_in_log = 0
484
- self.total_events_in_calendar = 0
485
-
486
- self.res_count_events_in_calendar = dict()
487
- self.res_count_events_in_log = dict()
488
- self.active_granules_in_calendar = dict()
489
- self.active_weekdays_in_calendar = dict()
490
- self.confidence_numerator_sum = dict()
491
- self.confidence_denominator_sum = dict()
492
-
493
- self.task_enabled_in_granule = dict()
494
-
495
- def register_resource_timestamp(self, r_name, t_name, date_time, is_joint=False):
496
- str_date, g_index, weekday = self.split_datetime(date_time)
497
-
498
- if r_name not in self.resource_freq:
499
- self.resource_freq[r_name] = 0
500
- self.resource_task_freq[r_name] = dict()
501
- self.res_active_weekdays[r_name] = dict()
502
- self.res_active_granules_weekdays[r_name] = dict()
503
- self.res_granules_frequency[r_name] = dict()
504
- self.active_res_task_weekdays[r_name] = dict()
505
- self.active_res_task_weekdays_granules[r_name] = dict()
506
- self.shared_task_granules[r_name] = dict()
507
- self.res_task_weekdays_granules_freq[r_name] = dict()
508
-
509
- if is_joint:
510
- self.joint_to_task[r_name] = t_name
511
-
512
- self.g_discarded[r_name] = list()
513
-
514
- self.res_count_events_in_calendar[r_name] = 0
515
- self.res_count_events_in_log[r_name] = 0
516
- self.active_granules_in_calendar[r_name] = set()
517
- self.active_weekdays_in_calendar[r_name] = set()
518
- self.confidence_numerator_sum[r_name] = 0
519
- self.confidence_denominator_sum[r_name] = 0
520
- self.is_joint_resource[r_name] = is_joint
521
-
522
- if t_name not in self.task_events_count:
523
- self.task_events_count[t_name] = 0
524
- self.task_events_in_calendar[t_name] = 0
525
- self.max_resource_task_freq[t_name] = 0
526
- if t_name not in self.resource_task_freq[r_name]:
527
- self.resource_task_freq[r_name][t_name] = 0
528
- self.active_res_task_weekdays[r_name][t_name] = dict()
529
- self.active_res_task_weekdays_granules[r_name][t_name] = dict()
530
- self.res_task_weekdays_granules_freq[r_name][t_name] = dict()
531
- if weekday not in self.active_res_task_weekdays[r_name][t_name]:
532
- self.active_res_task_weekdays[r_name][t_name][weekday] = set()
533
- self.active_res_task_weekdays_granules[r_name][t_name][weekday] = dict()
534
- if g_index not in self.active_res_task_weekdays_granules[r_name][t_name][weekday]:
535
- self.active_res_task_weekdays_granules[r_name][t_name][weekday][g_index] = set()
536
- if g_index not in self.res_task_weekdays_granules_freq[r_name][t_name]:
537
- self.res_task_weekdays_granules_freq[r_name][t_name][g_index] = dict()
538
- if weekday not in self.res_task_weekdays_granules_freq[r_name][t_name][g_index]:
539
- self.res_task_weekdays_granules_freq[r_name][t_name][g_index][weekday] = 0
540
- if weekday not in self.res_active_weekdays[r_name]:
541
- self.res_active_weekdays[r_name][weekday] = set()
542
- if g_index not in self.res_active_granules_weekdays[r_name]:
543
- self.res_active_granules_weekdays[r_name][g_index] = dict()
544
- self.res_granules_frequency[r_name][g_index] = dict()
545
- self.shared_task_granules[r_name][g_index] = dict()
546
- if weekday not in self.res_active_granules_weekdays[r_name][g_index]:
547
- self.res_active_granules_weekdays[r_name][g_index][weekday] = set()
548
- self.res_granules_frequency[r_name][g_index][weekday] = 0
549
- self.shared_task_granules[r_name][g_index][weekday] = set()
550
- if weekday not in self.observed_weekdays:
551
- self.observed_weekdays[weekday] = set()
552
-
553
- # Updating the weekdays and granules the resource was observed working
554
- self.res_active_weekdays[r_name][weekday].add(str_date)
555
- self.res_active_granules_weekdays[r_name][g_index][weekday].add(str_date)
556
- self.res_granules_frequency[r_name][g_index][weekday] += 1
557
-
558
- self.active_res_task_weekdays_granules[r_name][t_name][weekday][g_index].add(str_date)
559
- self.active_res_task_weekdays[r_name][t_name][weekday].add(str_date)
560
-
561
- self.resource_freq[r_name] += 1
562
- self.resource_task_freq[r_name][t_name] += 1
563
- self.res_count_events_in_log[r_name] += 1
564
- self.shared_task_granules[r_name][g_index][weekday].add(t_name)
565
- self.res_task_weekdays_granules_freq[r_name][t_name][g_index][weekday] += 1
566
-
567
- if not is_joint:
568
- self.max_resource_task_freq[t_name] = max(self.max_resource_task_freq[t_name],
569
- self.resource_task_freq[r_name][t_name])
570
- self.observed_weekdays[weekday].add(str_date)
571
- self.max_resource_freq = max(self.max_resource_freq, self.resource_freq[r_name])
572
- self.task_events_count[t_name] += 1
573
- self.total_events_in_log += 1
574
-
575
- def register_task_enablement(self, trace_events):
576
- self.res_enabled_task_granules = None
577
- for e_info in trace_events:
578
- t_name = e_info.task_name
579
- if t_name not in self.task_enabled_in_granule:
580
- self.task_enabled_in_granule[t_name] = dict()
581
- current_date = e_info.enabled_at
582
- str_date, g_index, weekday = self.split_datetime(current_date)
583
- while current_date < e_info.completed_at:
584
- if g_index not in self.task_enabled_in_granule[t_name]:
585
- self.task_enabled_in_granule[t_name][g_index] = dict()
586
- if weekday not in self.task_enabled_in_granule[t_name][g_index]:
587
- self.task_enabled_in_granule[t_name][g_index][weekday] = set()
588
- self.task_enabled_in_granule[t_name][g_index][weekday].add(str_date)
589
- current_date += timedelta(minutes=self.minutes_x_granule)
590
- if g_index >= self.total_granules - 1:
591
- str_date, g_index, weekday = self.split_datetime(current_date)
592
- else:
593
- g_index += 1
594
-
595
- def compute_resource_task_granule_enablement(self):
596
- self.res_enabled_task_granules = dict()
597
- for r_name in self.resource_task_freq:
598
- self.res_enabled_task_granules[r_name] = dict()
599
- joint_granules = dict()
600
- for t_name in self.resource_task_freq[r_name]:
601
- for g_index in self.task_enabled_in_granule[t_name]:
602
- if g_index not in self.res_enabled_task_granules[r_name]:
603
- joint_granules[g_index] = dict()
604
- self.res_enabled_task_granules[r_name][g_index] = dict()
605
- for weekday in self.task_enabled_in_granule[t_name][g_index]:
606
- if weekday not in self.res_enabled_task_granules[r_name][g_index]:
607
- self.res_enabled_task_granules[r_name][g_index][weekday] = set()
608
- joint_granules[g_index][weekday] = set()
609
- joint_granules[g_index][weekday] |= self.task_enabled_in_granule[t_name][g_index][weekday]
610
- for g_index in joint_granules:
611
- for weekday in joint_granules[g_index]:
612
- self.res_enabled_task_granules[r_name][g_index][weekday] = len(joint_granules[g_index][weekday])
613
-
614
- def enablement_confidence(self, r_name, weekday, g_index):
615
- if self.res_enabled_task_granules is None:
616
- self.compute_resource_task_granule_enablement()
617
- return len(self.res_active_granules_weekdays[r_name][g_index][weekday]) / \
618
- self.res_enabled_task_granules[r_name][g_index][weekday]
619
-
620
- def task_cond_confidence(self, r_name, weekday, g_index):
621
- best_task = None
622
- max_conf_val = 0
623
- task_confidences = dict()
624
- for t_name in self.shared_task_granules[r_name][g_index][weekday]:
625
- task_confidences[t_name] = len(self.active_res_task_weekdays_granules[r_name][t_name][weekday][g_index]) \
626
- / len(self.active_res_task_weekdays[r_name][t_name][weekday])
627
- if max_conf_val < task_confidences[t_name]:
628
- best_task = t_name
629
- max_conf_val = task_confidences[t_name]
630
- return best_task, task_confidences
631
-
632
- def resource_participation_ratio(self, r_name):
633
- total_res = 0
634
- total_max = 0
635
- for t_name in self.resource_task_freq[r_name]:
636
- total_res += self.resource_task_freq[r_name][t_name]
637
- total_max += self.max_resource_task_freq[t_name]
638
- return total_res / total_max if total_max > 0 else 0
639
-
640
- def resource_task_participation_ratio(self, r_name, t_name):
641
- if self.max_resource_task_freq[t_name] > 0:
642
- return self.resource_task_freq[r_name][t_name] / self.max_resource_task_freq[t_name]
643
- return 0
644
-
645
- # From all the WeekDays the resource was active, in which ration they were in the given granule
646
- def confidence(self, r_name, weekday, g_index):
647
- return len(self.res_active_granules_weekdays[r_name][g_index][weekday]) / len(
648
- self.res_active_weekdays[r_name][weekday])
649
-
650
- def support(self, r_name, weekday, g_index):
651
- return len(self.res_active_granules_weekdays[r_name][g_index][weekday]) / len(self.observed_weekdays[weekday])
652
-
653
- def weekday_support(self, r_name, weekday):
654
- return len(self.res_active_weekdays[r_name][weekday]) / len(self.observed_weekdays[weekday])
655
-
656
- def task_coverage(self, t_name):
657
- return self.task_events_in_calendar[t_name] / self.task_events_count[t_name]
658
-
659
- def can_improve_support(self, r_name, weekday, g_index, min_confidence):
660
- best_task, confidence_values = self.task_cond_confidence(r_name, weekday, g_index)
661
- if r_name == 'arrival':
662
- return best_task
663
-
664
- # new_granules = 0
665
- # new_weekdays = 0
666
-
667
- # for str_date in self.active_res_task_weekdays_granules[r_name][best_task][weekday][g_index]:
668
- # if str_date not in self.active_granules_in_calendar[r_name]:
669
- # new_granules += 1
670
- # for str_date in self.active_res_task_weekdays[r_name][best_task][weekday]:
671
- # if str_date not in self.active_weekdays_in_calendar[r_name]:
672
- # new_weekdays += 1
673
-
674
- # if min_confidence <= (len(self.active_granules_in_calendar[r_name]) + new_granules) \
675
- # / (len(self.active_weekdays_in_calendar[r_name]) + new_weekdays):
676
-
677
- # new_granules = len(self.active_res_task_weekdays_granules[r_name][best_task][weekday][g_index])
678
- # new_weekdays = len(self.active_res_task_weekdays[r_name][best_task][weekday])
679
- # if min_confidence <= (self.confidence_numerator_sum[r_name] + new_granules) \
680
- # / (self.confidence_denominator_sum[r_name] + new_weekdays):
681
- return best_task
682
-
683
- def reset_calendar_info(self):
684
- self.total_events_in_calendar = 0
685
- for t_name in self.task_events_count:
686
- self.task_events_in_calendar[t_name] = 0
687
- for r_name in self.shared_task_granules:
688
- self.res_count_events_in_calendar[r_name] = 0
689
- self.active_granules_in_calendar[r_name] = set()
690
- self.active_weekdays_in_calendar[r_name] = set()
691
- self.g_discarded[r_name] = list()
692
- self.confidence_numerator_sum[r_name] = 0
693
- self.confidence_denominator_sum[r_name] = 0
694
-
695
- def check_accepted_granule(self, r_name, weekday, g_index, best_task):
696
- self.res_count_events_in_calendar[r_name] += self.res_granules_frequency[r_name][g_index][weekday]
697
- self.total_events_in_calendar += self.res_granules_frequency[r_name][g_index][weekday]
698
- self.confidence_numerator_sum[r_name] += len(
699
- self.active_res_task_weekdays_granules[r_name][best_task][weekday][g_index])
700
- self.confidence_denominator_sum[r_name] += len(self.active_res_task_weekdays[r_name][best_task][weekday])
701
- self.active_granules_in_calendar[r_name] |= \
702
- self.active_res_task_weekdays_granules[r_name][best_task][weekday][g_index]
703
- self.active_weekdays_in_calendar[r_name] |= self.active_res_task_weekdays[r_name][best_task][weekday]
704
- for t_name in self.shared_task_granules[r_name][g_index][weekday]:
705
- self.task_events_in_calendar[t_name] += self.res_task_weekdays_granules_freq[r_name][t_name][g_index][
706
- weekday]
707
-
708
- def check_discarded_granule(self, r_name, weekday, g_index):
709
- if r_name not in self.g_discarded:
710
- self.g_discarded[r_name] = list()
711
- self.g_discarded[r_name].append(GranuleInfo(weekday, g_index))
712
-
713
- def update_discarded_granules_list(self, r_name, accepted_indexes):
714
- if len(accepted_indexes) == 0:
715
- return
716
- new_discarded = list()
717
- c_j = 0
718
- for i in range(0, len(self.g_discarded[r_name])):
719
- if c_j < len(accepted_indexes) and i == accepted_indexes[c_j]:
720
- c_j += 1
721
- else:
722
- new_discarded.append(self.g_discarded[r_name][i])
723
- self.g_discarded[r_name] = new_discarded
724
-
725
- def split_datetime(self, date_time):
726
- str_date = str(date_time.date())
727
- in_minutes = date_time.hour * 60 + date_time.minute
728
-
729
- g_index = in_minutes // self.minutes_x_granule
730
- week_day = date_time.weekday()
731
- return str_date, g_index, week_day
732
-
733
- def compute_confidence_support(self, r_name):
734
- if r_name not in self.active_weekdays_in_calendar \
735
- or len(self.active_weekdays_in_calendar[r_name]) == 0 or self.res_count_events_in_log[r_name] == 0:
736
- return 0, 0
737
- return self.confidence_numerator_sum[r_name] / self.confidence_denominator_sum[r_name], \
738
- self.res_count_events_in_calendar[r_name] / self.res_count_events_in_log[r_name]
739
- # return len(self.active_granules_in_calendar[r_name]) / len(self.active_weekdays_in_calendar[r_name]), \
740
- # self.res_count_events_in_calendar[r_name] / self.res_count_events_in_log[r_name]
741
-
742
-
743
- class GranuleInfo:
744
- def __init__(self, week_day, g_index):
745
- self.week_day = week_day
746
- self.g_index = g_index
747
-
748
-
749
- class CalendarFactory:
750
- def __init__(self, minutes_x_granule=15):
751
- if 1440 % minutes_x_granule != 0:
752
- raise ValueError(
753
- "The number of minutes per granule must be a divisor of the total minutes in one day (1440).")
754
-
755
- self.kpi_calendar = CalendarKPIInfoFactory(minutes_x_granule)
756
-
757
- self.minutes_x_granule = minutes_x_granule
758
-
759
- self.from_datetime = datetime.datetime(9999, 12, 31, tzinfo=pytz.UTC)
760
- self.to_datetime = datetime.datetime(1, 1, 1, tzinfo=pytz.UTC)
761
-
762
- def _init_calendar_tree(self):
763
- granules_count = 1440 // self.minutes_x_granule
764
- return [[0] * granules_count for _i in range(0, 7)]
765
-
766
- def register_task_enablement(self, trace_events):
767
- self.kpi_calendar.register_task_enablement(trace_events)
768
-
769
- def check_date_time(self, r_name, t_name, date_time, is_joint=False):
770
- self.kpi_calendar.register_resource_timestamp(r_name, t_name, date_time, is_joint)
771
-
772
- self.from_datetime = min(self.from_datetime, date_time)
773
- self.to_datetime = max(self.to_datetime, date_time)
774
-
775
- def build_weekly_calendars(self, min_confidence, desired_support, min_participation):
776
- r_calendars = dict()
777
- self.kpi_calendar.reset_calendar_info()
778
- for r_name in self.kpi_calendar.shared_task_granules:
779
- if self.kpi_calendar.resource_participation_ratio(r_name) >= min_participation:
780
- r_calendars[r_name] = self.build_resource_calendar(r_name, min_confidence, desired_support)
781
- else:
782
- r_calendars[r_name] = None
783
- return r_calendars
784
-
785
- def build_resource_calendar(self, r_name, min_confidence, desired_support):
786
- r_calendar = RCalendar("%s_Schedule" % r_name)
787
- kpi_c = self.kpi_calendar
788
- to_print = dict()
789
-
790
- count = 0
791
- for g_index in kpi_c.shared_task_granules[r_name]:
792
- for weekday in kpi_c.shared_task_granules[r_name][g_index]:
793
- best_task, conf_values = kpi_c.task_cond_confidence(r_name, weekday, g_index)
794
- if min_confidence <= conf_values[best_task]:
795
- kpi_c.check_accepted_granule(r_name, weekday, g_index, best_task)
796
- self._add_calendar_item(weekday, g_index, r_calendar)
797
- else:
798
- count += 1
799
- if weekday not in to_print:
800
- to_print[weekday] = list()
801
- to_print[weekday].append(g_index)
802
- kpi_c.g_discarded[r_name].append(GranuleInfo(weekday, g_index))
803
-
804
- confidence, support = kpi_c.compute_confidence_support(r_name)
805
-
806
- if confidence > 0 and support < desired_support:
807
- kpi_c.g_discarded[r_name].sort(key=lambda x: kpi_c.res_granules_frequency[r_name][x.g_index][x.week_day],
808
- reverse=True)
809
- accepted_indexes = list()
810
- i = 0
811
-
812
- for g_info in kpi_c.g_discarded[r_name]:
813
- best_task = kpi_c.can_improve_support(r_name, g_info.week_day, g_info.g_index, min_confidence)
814
- if best_task is not None:
815
- self._add_calendar_item(g_info.week_day, g_info.g_index, r_calendar)
816
- kpi_c.check_accepted_granule(r_name, g_info.week_day, g_info.g_index, best_task)
817
- accepted_indexes.append(i)
818
- _, support = kpi_c.compute_confidence_support(r_name)
819
- if support >= desired_support:
820
- break
821
- i += 1
822
- kpi_c.update_discarded_granules_list(r_name, accepted_indexes)
823
- return r_calendar
824
-
825
- def task_coverage(self, t_name):
826
- return self.kpi_calendar.task_coverage(t_name)
827
-
828
- def build_unrestricted_resource_calendar(self, r_name, t_name):
829
- r_calendar = RCalendar("%s_Schedule" % r_name)
830
-
831
- r_kpi = self.kpi_calendar
832
- for g_index in r_kpi.res_active_granules_weekdays[r_name]:
833
- for week_day in r_kpi.res_active_granules_weekdays[r_name][g_index]:
834
- r_kpi.check_accepted_granule(r_name, week_day, g_index, t_name)
835
- self._add_calendar_item(week_day, g_index, r_calendar)
836
- return r_calendar
837
-
838
- def _add_calendar_item(self, week_day, g_index, r_calendar):
839
- str_wday = int_week_days[week_day]
840
- hour = (g_index * self.minutes_x_granule) // 60
841
- from_min = (g_index * self.minutes_x_granule) % 60
842
- to_min = from_min + self.minutes_x_granule
843
- if to_min >= 60:
844
- if hour == 23:
845
- r_calendar.add_calendar_item(str_wday, str_wday, "%d:%d:%d" % (hour, from_min, 0), "23:59:59.999")
846
- else:
847
- r_calendar.add_calendar_item(str_wday, str_wday, "%d:%d:%d" % (hour, from_min, 0),
848
- "%d:%d:%d" % (hour + 1, 0, 0))
849
- else:
850
- r_calendar.add_calendar_item(str_wday, str_wday, "%d:%d:%d" % (hour, from_min, 0),
851
- "%d:%d:%d" % (hour, to_min, 0))
852
-
853
-
854
- def build_full_time_calendar(calendar_id):
855
- r_calendar = RCalendar(calendar_id)
856
- for i in range(0, 7):
857
- str_weekday = int_week_days[i]
858
- r_calendar.add_calendar_item(str_weekday, str_weekday, "00:00:00.000", "23:59:59.999")
859
- return r_calendar
860
-
861
-
862
- def update_calendar_from_log(r_calendar, date_time, is_start, month_freq, min_eps=15):
863
- if date_time.month not in month_freq:
864
- month_freq[date_time.month] = [0, set()]
865
- month_freq[date_time.month][0] += 1
866
- month_freq[date_time.month][1].add(date_time.year)
867
-
868
- from_date = date_time
869
- to_date = date_time
870
- if is_start:
871
- to_date = date_time + timedelta(minutes=min_eps)
872
- else:
873
- from_date = date_time - timedelta(minutes=min_eps)
874
-
875
- from_day = int_week_days[from_date.weekday()]
876
- to_day = int_week_days[to_date.weekday()]
877
-
878
- if from_day != to_day:
879
- r_calendar.add_calendar_item(from_day, from_day,
880
- "%d:%d:%d" % (from_date.hour, from_date.minute, from_date.second), "23:59:59.999")
881
- if to_date.hour != 0 or to_date.minute != 0 or to_date.second != 0:
882
- r_calendar.add_calendar_item(to_day, to_day, "00:00:00",
883
- "%d:%d:%d" % (to_date.hour, to_date.minute, to_date.second))
884
- else:
885
- r_calendar.add_calendar_item(from_day, to_day,
886
- "%d:%d:%d" % (from_date.hour, from_date.minute, from_date.second),
887
- "%d:%d:%d" % (to_date.hour, to_date.minute, to_date.second))
888
-
889
-
890
- def _worked_days_count(from_date, to_date):
891
- total_days = (to_date.date() - from_date.date()).days + 1
892
- total_days_count = dict()
893
- for i in range(0, 7):
894
- total_days_count[i] = int(total_days / 7)
895
- total_days %= 7
896
- for i in range(from_date.weekday(), from_date.weekday() + total_days):
897
- total_days_count[i % 7] += 1
898
- return total_days_count
899
-
900
-
901
- def compute_total_weekdays(from_datetime, to_datetime):
902
- total_days = (to_datetime.date() - from_datetime.date()).days + 2
903
- same_days = total_days // 7
904
- rem_days = total_days % 7
905
- to_weekday = to_datetime.weekday()
906
- total_weekdays = {key: same_days for key in range(0, 7)}
907
- while rem_days > 0:
908
- total_weekdays[to_weekday] += 1
909
- to_weekday = to_weekday - 1 if to_weekday > 0 else 6
910
- rem_days -= 1
911
- return total_weekdays
File without changes
File without changes
File without changes