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.
- {prosimos-1.2.3 → prosimos-1.2.4}/PKG-INFO +8 -4
- {prosimos-1.2.3 → prosimos-1.2.4}/README.md +5 -1
- {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/log_parser.py +2 -1
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/batch_processing.py +6 -5
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_engine.py +30 -9
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_properties_parser.py +1 -1
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_setup.py +8 -8
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_stats_calculator.py +1 -1
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/weekday_helper.py +3 -1
- {prosimos-1.2.3 → prosimos-1.2.4}/pyproject.toml +3 -2
- prosimos-1.2.3/prosimos/resource_calendar.py +0 -911
- {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/__init__.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/emd_metric.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/exceptions.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/inter_arrival_cases_discovery.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/bpdfr_discovery/log_comparison_metrics.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/cli/__init__.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/cli/diff_res_bpsim.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/__init__.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/batch_processing_parser.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/case_attributes.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/control_flow_manager.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/exceptions.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/execution_info.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/file_manager.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/prioritisation.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/prioritisation_parser.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/prioritisation_rules.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/probability_distributions.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/resource_profile.py +0 -0
- {prosimos-1.2.3 → prosimos-1.2.4}/prosimos/simulation_queues_ds.py +0 -0
- {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
|
+
Version: 1.2.4
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Iryna Halenok, Orlenys López Pintado
|
|
6
|
-
Requires-Python: >=3.
|
|
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
|
+

|
|
24
|
+

|
|
25
|
+

|
|
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.
|
|
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
|
+

|
|
4
|
+

|
|
5
|
+

|
|
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.
|
|
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
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from random import choices
|
|
6
|
+
from typing import List
|
|
8
7
|
|
|
9
|
-
from
|
|
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
|
|
6
|
-
|
|
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
|
|
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] =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "prosimos"
|
|
3
|
-
version = "1.2.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|