| | from random import Random |
| | from datetime import datetime, timedelta |
| | from enum import Enum |
| | from typing import List |
| | from dataclasses import dataclass |
| |
|
| | from .domain import Person, TimeGrain, Room, Meeting, MeetingAssignment, MeetingSchedule, RequiredAttendance, PreferredAttendance |
| |
|
| | class DemoData(str, Enum): |
| | SMALL = "SMALL" |
| | MEDIUM = "MEDIUM" |
| | LARGE = "LARGE" |
| |
|
| | @dataclass(frozen=True, kw_only=True) |
| | class CountDistribution: |
| | count: int |
| | weight: float |
| |
|
| | def counts(distributions: tuple[CountDistribution, ...]) -> tuple[int, ...]: |
| | return tuple(distribution.count for distribution in distributions) |
| |
|
| | def weights(distributions: tuple[CountDistribution, ...]) -> tuple[float, ...]: |
| | return tuple(distribution.weight for distribution in distributions) |
| |
|
| | def generate_demo_data() -> MeetingSchedule: |
| | """Generate demo data for the meeting scheduling problem.""" |
| | rnd = Random(0) |
| | |
| | |
| | people = generate_people(20, rnd) |
| | |
| | |
| | time_grains = generate_time_grains() |
| | |
| | |
| | rooms = [ |
| | Room(id="R1", name="Room 1", capacity=30), |
| | Room(id="R2", name="Room 2", capacity=20), |
| | Room(id="R3", name="Room 3", capacity=16) |
| | ] |
| | |
| | |
| | meetings = generate_meetings(people, rnd) |
| | |
| | |
| | all_required_attendances = [ra for meeting in meetings for ra in meeting.required_attendances] |
| | all_preferred_attendances = [pa for meeting in meetings for pa in meeting.preferred_attendances] |
| | new_meetings = [] |
| | for m in meetings: |
| | new_meetings.append( |
| | type(m)( |
| | id=m.id, |
| | topic=m.topic, |
| | duration_in_grains=m.duration_in_grains, |
| | speakers=m.speakers, |
| | content=m.content or "", |
| | entire_group_meeting=m.entire_group_meeting, |
| | required_attendances=[a for a in all_required_attendances if a.meeting_id == m.id], |
| | preferred_attendances=[a for a in all_preferred_attendances if a.meeting_id == m.id], |
| | ) |
| | ) |
| | meetings = new_meetings |
| | |
| | |
| | meeting_assignments = generate_meeting_assignments(meetings) |
| | |
| | |
| | schedule = MeetingSchedule( |
| | people=people, |
| | time_grains=time_grains, |
| | rooms=rooms, |
| | meetings=meetings, |
| | meeting_assignments=meeting_assignments, |
| | required_attendances=[ra for meeting in meetings for ra in meeting.required_attendances], |
| | preferred_attendances=[pa for meeting in meetings for pa in meeting.preferred_attendances], |
| | ) |
| | |
| | return schedule |
| |
|
| |
|
| | def generate_people(count_people: int, rnd: Random) -> List[Person]: |
| | """Generate a list of people.""" |
| | FIRST_NAMES = ["Amy", "Beth", "Carl", "Dan", "Elsa", "Flo", "Gus", "Hugo", "Ivy", "Jay", |
| | "Jeri", "Hope", "Avis", "Lino", "Lyle", "Nick", "Dino", "Otha", "Gwen", "Jose", |
| | "Dena", "Jana", "Dave", "Russ", "Josh", "Dana", "Katy"] |
| | LAST_NAMES = ["Cole", "Fox", "Green", "Jones", "King", "Li", "Poe", "Rye", "Smith", "Watt", |
| | "Howe", "Lowe", "Wise", "Clay", "Carr", "Hood", "Long", "Horn", "Haas", "Meza"] |
| | |
| | def generate_name() -> str: |
| | first_name = rnd.choice(FIRST_NAMES) |
| | last_name = rnd.choice(LAST_NAMES) |
| | return f"{first_name} {last_name}" |
| | |
| | return [Person(id=str(i), full_name=generate_name()) for i in range(count_people)] |
| |
|
| |
|
| | def generate_time_grains() -> List[TimeGrain]: |
| | """Generate time grains for the next 4 days starting from tomorrow.""" |
| | time_grains = [] |
| | current_date = datetime.now().date() + timedelta(days=1) |
| | count = 0 |
| | |
| | while current_date < datetime.now().date() + timedelta(days=5): |
| | current_time = datetime.combine(current_date, datetime.min.time()) + timedelta(hours=8) |
| | end_time = datetime.combine(current_date, datetime.min.time()) + timedelta(hours=17, minutes=45) |
| | |
| | while current_time <= end_time: |
| | day_of_year = current_date.timetuple().tm_yday |
| | minutes_of_day = current_time.hour * 60 + current_time.minute |
| | |
| | count += 1 |
| | time_grains.append(TimeGrain( |
| | id=str(count), |
| | grain_index=count, |
| | day_of_year=day_of_year, |
| | starting_minute_of_day=minutes_of_day |
| | )) |
| | current_time += timedelta(minutes=15) |
| | |
| | current_date += timedelta(days=1) |
| | |
| | return time_grains |
| |
|
| |
|
| | def generate_meetings(people: List[Person], rnd: Random) -> List[Meeting]: |
| | """Generate meetings with topics and attendees.""" |
| | meeting_topics = [ |
| | "Strategize B2B", "Fast track e-business", "Cross sell virtualization", |
| | "Profitize multitasking", "Transform one stop shop", "Engage braindumps", |
| | "Downsize data mining", "Ramp up policies", "On board synergies", |
| | "Reinvigorate user experience", "Strategize e-business", "Fast track virtualization", |
| | "Cross sell multitasking", "Profitize one stop shop", "Transform braindumps", |
| | "Engage data mining", "Downsize policies", "Ramp up synergies", |
| | "On board user experience", "Reinvigorate B2B", "Strategize virtualization", |
| | "Fast track multitasking", "Cross sell one stop shop", "Reinvigorate multitasking" |
| | ] |
| | |
| | meetings = [] |
| | for i, topic in enumerate(meeting_topics): |
| | meeting = Meeting(id=str(i), topic=topic, duration_in_grains=0) |
| | meetings.append(meeting) |
| | |
| | |
| | duration_distribution = ( |
| | CountDistribution(count=8, weight=1), |
| | CountDistribution(count=12, weight=1), |
| | CountDistribution(count=16, weight=1) |
| | ) |
| |
|
| | for meeting in meetings: |
| | duration_time_grains, = rnd.choices(population=counts(duration_distribution), |
| | weights=weights(duration_distribution)) |
| | meeting.duration_in_grains = duration_time_grains |
| | |
| | |
| | required_attendees_distribution = ( |
| | CountDistribution(count=2, weight=0.45), |
| | CountDistribution(count=3, weight=0.15), |
| | CountDistribution(count=4, weight=0.10), |
| | CountDistribution(count=5, weight=0.10), |
| | CountDistribution(count=6, weight=0.08), |
| | CountDistribution(count=7, weight=0.05), |
| | CountDistribution(count=8, weight=0.04), |
| | CountDistribution(count=10, weight=0.03) |
| | ) |
| |
|
| | def add_required_attendees(meeting: Meeting, count: int) -> None: |
| | |
| | selected_people = rnd.sample(people, count) |
| | for person in selected_people: |
| | meeting.required_attendances.append( |
| | RequiredAttendance( |
| | id=f"{meeting.id}-{len(meeting.required_attendances) + 1}", |
| | person=person, |
| | meeting_id=meeting.id |
| | ) |
| | ) |
| |
|
| | for meeting in meetings: |
| | count, = rnd.choices(population=counts(required_attendees_distribution), |
| | weights=weights(required_attendees_distribution)) |
| | add_required_attendees(meeting, count) |
| | |
| | |
| | preferred_attendees_distribution = ( |
| | CountDistribution(count=1, weight=0.25), |
| | CountDistribution(count=2, weight=0.30), |
| | CountDistribution(count=3, weight=0.20), |
| | CountDistribution(count=4, weight=0.10), |
| | CountDistribution(count=5, weight=0.06), |
| | CountDistribution(count=6, weight=0.04), |
| | CountDistribution(count=7, weight=0.02), |
| | CountDistribution(count=8, weight=0.02), |
| | CountDistribution(count=9, weight=0.01), |
| | CountDistribution(count=10, weight=0.00) |
| | ) |
| |
|
| | def add_preferred_attendees(meeting: Meeting, count: int) -> None: |
| | |
| | required_people_ids = {ra.person.id for ra in meeting.required_attendances} |
| | available_people = [person for person in people if person.id not in required_people_ids] |
| | |
| | |
| | if len(available_people) >= count: |
| | selected_people = rnd.sample(available_people, count) |
| | for person in selected_people: |
| | meeting.preferred_attendances.append( |
| | PreferredAttendance( |
| | id=f"{meeting.id}-{len(meeting.required_attendances) + len(meeting.preferred_attendances) + 1}", |
| | person=person, |
| | meeting_id=meeting.id |
| | ) |
| | ) |
| | |
| | for meeting in meetings: |
| | count, = rnd.choices(population=counts(preferred_attendees_distribution), |
| | weights=weights(preferred_attendees_distribution)) |
| | add_preferred_attendees(meeting, count) |
| | |
| | return meetings |
| |
|
| |
|
| | def generate_meeting_assignments(meetings: List[Meeting]) -> List[MeetingAssignment]: |
| | """Generate meeting assignments for each meeting.""" |
| | return [MeetingAssignment(id=str(i), meeting=meeting) for i, meeting in enumerate(meetings)] |
| |
|