File size: 4,038 Bytes
50f82a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
from datetime import date, timedelta
from enum import Enum
from random import Random
from typing import List

from .domain import (
    Crew,
    Job,
    MaintenanceSchedule,
    WorkCalendar,
    calculate_end_date,
    create_start_date_range,
)


class DemoData(Enum):
    SMALL = "SMALL"
    LARGE = "LARGE"


# Job area names for generating job names
JOB_AREA_NAMES = [
    "Downtown", "Uptown", "Park", "Airport", "Bay", "Hill", "Forest", "Station",
    "Hospital", "Harbor", "Market", "Fort", "Beach", "Garden", "River", "Springs",
    "Tower", "Mountain"
]

# Job target names for generating job names
JOB_TARGET_NAMES = [
    "Street", "Bridge", "Tunnel", "Highway", "Boulevard", "Avenue", "Square", "Plaza"
]


def _get_next_monday(from_date: date) -> date:
    """Get the next Monday on or after the given date."""
    days_until_monday = (7 - from_date.weekday()) % 7
    if days_until_monday == 0 and from_date.weekday() != 0:
        days_until_monday = 7
    return from_date + timedelta(days=days_until_monday)


def generate_demo_data(demo_data: DemoData) -> MaintenanceSchedule:
    """
    Generate demo data for the maintenance scheduling problem.

    Args:
        demo_data: The demo data type (SMALL or LARGE)

    Returns:
        A MaintenanceSchedule with crews, work calendar, and jobs
    """
    # Create crews
    crews: List[Crew] = [
        Crew(id="1", name="Alpha crew"),
        Crew(id="2", name="Beta crew"),
        Crew(id="3", name="Gamma crew"),
    ]
    if demo_data == DemoData.LARGE:
        crews.append(Crew(id="4", name="Delta crew"))
        crews.append(Crew(id="5", name="Epsilon crew"))

    # Create work calendar
    from_date = _get_next_monday(date.today())
    week_list_size = 16 if demo_data == DemoData.LARGE else 8
    to_date = from_date + timedelta(weeks=week_list_size)
    work_calendar = WorkCalendar(id="1", from_date=from_date, to_date=to_date)

    workday_total = week_list_size * 5

    # Create jobs
    jobs: List[Job] = []
    job_list_size = week_list_size * len(crews) * 3 // 5
    job_area_target_limit = min(len(JOB_TARGET_NAMES), len(crews) * 2)
    random = Random(17)  # Same seed as Java

    for i in range(job_list_size):
        job_area = JOB_AREA_NAMES[i // job_area_target_limit]
        job_target = JOB_TARGET_NAMES[i % job_area_target_limit]

        # 1 day to 2 workweeks (1 workweek on average)
        duration_in_days = 1 + random.randint(0, 9)

        # Calculate date constraints with at least 5 days of flexibility
        min_max_between_workdays = (
            duration_in_days + 5
            + random.randint(0, workday_total - (duration_in_days + 5) - 1)
        )
        min_workday_offset = random.randint(0, workday_total - min_max_between_workdays)
        min_ideal_end_between_workdays = min_max_between_workdays - 1 - random.randint(0, 3)

        # Calculate dates using the weekend-skipping calculation
        min_start_date = calculate_end_date(from_date, min_workday_offset)
        max_end_date = calculate_end_date(min_start_date, min_max_between_workdays)
        ideal_end_date = calculate_end_date(min_start_date, min_ideal_end_between_workdays)

        # 10% chance of having "Subway" tag
        if random.random() < 0.1:
            tags = {job_area, "Subway"}
        else:
            tags = {job_area}

        jobs.append(Job(
            id=str(i),
            name=f"{job_area} {job_target}",
            duration_in_days=duration_in_days,
            min_start_date=min_start_date,
            max_end_date=max_end_date,
            ideal_end_date=ideal_end_date,
            tags=tags,
        ))

    # Create and return the schedule
    schedule = MaintenanceSchedule(
        work_calendar=work_calendar,
        crews=crews,
        jobs=jobs,
    )

    # The start_date_range will be auto-populated by __post_init__
    # But let's ensure it's populated
    if not schedule.start_date_range:
        schedule.start_date_range = create_start_date_range(from_date, to_date)

    return schedule