Skip to content

Commit a609437

Browse files
psvenkdtemkin1
andauthored
Miscellaneous fixes relating to PE classes (#293)
Some finishing touches on the PE classes feature (prev: #284). --------- Co-authored-by: Diego Temkin <65834932+dtemkin1@users.noreply.github.com>
1 parent 71c4822 commit a609437

File tree

12 files changed

+174
-140
lines changed

12 files changed

+174
-140
lines changed

package-lock.json

Lines changed: 0 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scrapers/pe.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"Fencing",
3838
"Pistol",
3939
"Air Pistol",
40-
"Rifle", # TODO ask if air rifle is also eligible
40+
"Rifle",
41+
"Air Rifle",
4142
"Sailing",
4243
]
4344

@@ -84,6 +85,7 @@ class PEWSchema(TypedDict):
8485
wellness: bool
8586
pirate: bool
8687
swimGIR: bool
88+
remote: bool
8789
prereqs: str
8890
equipment: str
8991
fee: str
@@ -125,7 +127,7 @@ def augment_location(location: str) -> str:
125127
str: The location, with a building number possibly prepended
126128
127129
>>> augment_location("Du Pont T Club Lounge")
128-
'W35 - Du Pont T Club Lounge'
130+
'W35+ - Du Pont T Club Lounge'
129131
130132
>>> augment_location("Harvard")
131133
'Harvard'
@@ -134,10 +136,10 @@ def augment_location(location: str) -> str:
134136
'Du Pont T Club Lounge and 26-100'
135137
"""
136138
buildings = {
137-
"Du Pont": "W35",
138-
"Zesiger": "W35",
139-
"Rockwell": "W35",
140-
"Johnson": "W35",
139+
"Du Pont": "W35+",
140+
"Zesiger": "W35+",
141+
"Rockwell": "W35+",
142+
"Johnson": "W35+",
141143
}
142144

143145
if " and " in location:
@@ -261,7 +263,7 @@ def parse_date(date_str: str) -> date:
261263

262264
def parse_times_to_raw_section(start_time: str, days: str, location: str) -> str:
263265
"""
264-
Parses times from CVS to format from Fireroad, for compatibility.
266+
Parses times from CSV to Fireroad format, for compatibility.
265267
266268
Args:
267269
start_time (str): Start time of the class
@@ -273,21 +275,16 @@ def parse_times_to_raw_section(start_time: str, days: str, location: str) -> str
273275
"""
274276
start_c = time_c.strptime(start_time, "%I:%M %p")
275277
start = time(start_c.tm_hour, start_c.tm_min)
276-
end = time(
277-
start.hour + 1, start.minute
278-
) # default to 1 hour, can be changed in overrides
278+
# default to 1 hour, can be changed in overrides
279279

280280
start_raw_time = (
281-
f"{12 - ((- start.hour) % 12)}" f"{'.30' if start.minute > 29 else ''}"
281+
f"{12 - ((- start.hour) % 12)}"
282+
f"{'.30' if start.minute > 29 else ''}"
283+
f"{' PM' if start.hour >= 17 else ''}"
282284
)
283-
end_raw_time = (
284-
f"{12 - ((- end.hour) % 12)}"
285-
f"{'.30' if end.minute > 29 else ''}"
286-
f"{' PM' if end.hour >= 17 else ''}"
287-
)
288-
evening = "1" if end.hour >= 17 else "0"
285+
evening = "1" if start.hour >= 17 else "0"
289286

290-
return f"{location}/{days}/{evening}/{start_raw_time}-{end_raw_time}"
287+
return f"{location}/{days}/{evening}/{start_raw_time}"
291288

292289

293290
def parse_data(row: PEWFile, quarter: int) -> PEWSchema:
@@ -322,6 +319,7 @@ def parse_data(row: PEWFile, quarter: int) -> PEWSchema:
322319
"wellness": any(number.startswith(prefix) for prefix in WELLNESS_PREFIXES),
323320
"pirate": any(row["Title"].startswith(prefix) for prefix in PIRATE_CLASSES),
324321
"swimGIR": parse_bool(row["Swim GIR"]),
322+
"remote": row["Title"].lower().find("remote") != -1,
325323
"prereqs": row["Prerequisites"] or "None",
326324
"equipment": row["Equipment"],
327325
"fee": row["Fee Amount"],

scrapers/pe/pe-q3.csv

Lines changed: 64 additions & 62 deletions
Large diffs are not rendered by default.

src/components/ActivityDescription.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ function PEClassTypes(props: { cls: PEClass }) {
166166
["wellness", "Wellness Wizard eligible"],
167167
["pirate", "Pirate Certificate eligible"],
168168
["swim", "Satisfies swim GIR"],
169+
["remote", "Remote class"],
169170
]);
170171

171172
return (

src/components/Calendar.tsx

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,26 @@ export function Calendar() {
3535
.flatMap((event) => event.eventInputs);
3636
}, [selectedActivities]);
3737

38+
const getBuildingNumber = (room: string) =>
39+
room.split("-")[0].trim().replace(/\+$/, "");
40+
41+
/**
42+
* Get the approximate distance (in feet) between two buildings on campus
43+
*/
44+
const getDistance = (building1: string, building2: string) => {
45+
// Get coordinates of each building
46+
const location1 = state.locations.get(building1);
47+
const location2 = state.locations.get(building2);
48+
49+
if (!location1 || !location2) {
50+
return undefined;
51+
}
52+
53+
const dx = location1.x - location2.x;
54+
const dy = location1.y - location2.y;
55+
return Math.sqrt(dx * dx + dy * dy);
56+
};
57+
3858
/**
3959
* Check if event1 ends at the same time that some other event starts. If
4060
* this is the case and the commute distance between the two events' locations
@@ -55,26 +75,13 @@ export function Calendar() {
5575
continue;
5676
}
5777

58-
// Extract building numbers from room numbers
59-
const building1 = room1.split("-")[0].trim();
60-
const building2 = event2.room.split("-")[0].trim();
61-
62-
// Get coordinates of each building
63-
const location1 = state.locations.get(building1);
64-
const location2 = state.locations.get(building2);
65-
66-
if (!location1 || !location2) {
67-
continue;
68-
}
78+
const building1 = getBuildingNumber(room1);
79+
const building2 = getBuildingNumber(event2.room);
6980

70-
// Approximate distance (in feet) between the two buildings using Pythagoras
71-
const distance = (() => {
72-
const dx = location1.x - location2.x;
73-
const dy = location1.y - location2.y;
74-
return Math.sqrt(dx * dx + dy * dy);
75-
})();
81+
// Approximate distance (in feet) between the two buildings
82+
const distance = getDistance(building1, building2);
7683

77-
if (distance < DISTANCE_WARNING_THRESHOLD) {
84+
if (distance === undefined || distance < DISTANCE_WARNING_THRESHOLD) {
7885
continue;
7986
}
8087

@@ -111,26 +118,39 @@ export function Calendar() {
111118
const distanceWarning = getDistanceWarning(event);
112119

113120
return (
114-
<Box
115-
color={event.textColor}
116-
p={0.5}
117-
lineHeight={1.3}
118-
cursor="pointer"
119-
height="100%"
120-
position="relative"
121-
>
122-
{!(activity instanceof CustomActivity) ? (
123-
<Tooltip
124-
content={activity.name}
125-
portalled
126-
positioning={{ placement: "top" }}
127-
>
128-
{TitleText()}
129-
</Tooltip>
130-
) : (
131-
<TitleText />
132-
)}
133-
<Text fontSize="xs">{room}</Text>
121+
<>
122+
<Box
123+
color={event.textColor}
124+
overflow="hidden"
125+
p={0.5}
126+
lineHeight={1.3}
127+
cursor="pointer"
128+
height="100%"
129+
position="relative"
130+
>
131+
{!(activity instanceof CustomActivity) ? (
132+
<Tooltip
133+
content={activity.name}
134+
portalled
135+
positioning={{ placement: "top" }}
136+
>
137+
{TitleText()}
138+
</Tooltip>
139+
) : (
140+
<TitleText />
141+
)}
142+
{event.extendedProps.roomClarification ? (
143+
<Tooltip
144+
content={event.extendedProps.roomClarification as string}
145+
portalled
146+
positioning={{ placement: "top" }}
147+
>
148+
<Text fontSize="xs">{room}</Text>
149+
</Tooltip>
150+
) : (
151+
<Text fontSize="xs">{room}</Text>
152+
)}
153+
</Box>
134154
{distanceWarning ? (
135155
<Float placement="bottom-end">
136156
<Tooltip
@@ -149,7 +169,7 @@ export function Calendar() {
149169
</Tooltip>
150170
</Float>
151171
) : null}
152-
</Box>
172+
</>
153173
);
154174
};
155175

src/components/PEClassTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ const CLASS_FLAGS_2: FilterGroup = [
222222
["wellness", "🔮 Wellness Wizard"],
223223
["pirate", "🏴‍☠️ Pirate Certificate"],
224224
["swim", "🌊 Swim GIR"],
225+
["remote", "💻 Remote"],
225226
];
226227

227228
const CLASS_FLAGS = [...CLASS_FLAGS_1, ...CLASS_FLAGS_2];
@@ -459,7 +460,6 @@ export function PEClassTable() {
459460
fee: cls.fee,
460461
name: cls.rawClass.name,
461462
class: cls,
462-
// TODO figure out if we get PE instructor names
463463
})),
464464
[peClasses],
465465
);

src/lib/activity.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ export class Event {
104104
slots: Timeslot[];
105105
/** The room of the event. */
106106
room?: string;
107+
/** Room clarification, e.g. "in the pool" */
108+
roomClarification?: string;
107109
/** If defined, 1 -> first half; 2 -> second half. */
108110
half?: number;
109111

@@ -113,12 +115,14 @@ export class Event {
113115
slots: Timeslot[],
114116
room?: string,
115117
half?: number,
118+
roomClarification?: string,
116119
) {
117120
this.activity = activity;
118121
this.name = name;
119122
this.slots = slots;
120123
this.room = room;
121124
this.half = half;
125+
this.roomClarification = roomClarification;
122126
}
123127

124128
/** List of events that can be directly given to FullCalendar. */
@@ -136,6 +140,7 @@ export class Event {
136140
backgroundColor: color,
137141
borderColor: color,
138142
room: this.room,
143+
roomClarification: this.roomClarification,
139144
activity: this.activity,
140145
}));
141146
}

src/lib/class.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export class Class implements BaseActivity {
229229
return this.number;
230230
}
231231

232-
/** Name, e.g. "Introduction to Machine Learning". */
232+
/** Name; e.g. "Introduction to Machine Learning". */
233233
get name(): string {
234234
if (this.rawClass.oldNumber) {
235235
return `[${this.rawClass.oldNumber}] ${this.rawClass.name}`;

src/lib/pe.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import { type RawPEClass, type RawSection } from "./raw";
33
import { Event } from "./activity";
44
import { fallbackColor, type ColorScheme } from "./colors";
55

6+
export const W35_PLUS_TEXT =
7+
"W31, W32, W33, W34 and W35 are all connected. Enter through W35.";
8+
69
export interface PEFlags {
710
wellness: boolean;
811
pirate: boolean;
912
swim: boolean;
13+
remote: boolean;
1014
nofee: boolean;
1115
nopreq: boolean;
1216
}
@@ -15,6 +19,7 @@ const peFlagEmojis: { [k in keyof PEFlags]?: string } = {
1519
wellness: "🔮",
1620
pirate: "🏴‍☠️",
1721
swim: "🌊",
22+
remote: "💻",
1823
};
1924

2025
export const getPEFlagEmoji = (flag: keyof PEFlags): string => {
@@ -64,6 +69,20 @@ export class PESections extends Sections {
6469
get priority(): number {
6570
return -1;
6671
}
72+
73+
get event(): Event | null {
74+
const room = this.roomOverride || this.selected?.room;
75+
return this.selected
76+
? new Event(
77+
this.cls,
78+
this.longName,
79+
this.selected.timeslots,
80+
room,
81+
undefined,
82+
room?.includes("W35+") ? W35_PLUS_TEXT : undefined,
83+
)
84+
: null;
85+
}
6786
}
6887

6988
/**
@@ -88,10 +107,12 @@ export class PEClass implements BaseActivity {
88107
];
89108
}
90109

110+
/** ID unique over all Activities. */
91111
get id(): string {
92112
return this.rawClass.number;
93113
}
94114

115+
/** Name; e.g. "Swimming, Beginner". */
95116
get name(): string {
96117
return this.rawClass.name;
97118
}
@@ -160,6 +181,7 @@ export class PEClass implements BaseActivity {
160181
wellness: this.rawClass.wellness,
161182
pirate: this.rawClass.pirate,
162183
swim: this.rawClass.swimGIR,
184+
remote: this.rawClass.remote,
163185
nofee: this.fee == 0,
164186
nopreq: this.rawClass.prereqs == "None",
165187
};

src/lib/raw.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ export interface RawPEClass {
229229
pirate: boolean;
230230
/** Satisfies swim GIR */
231231
swimGIR: boolean;
232+
/** Remote status */
233+
remote: boolean;
232234

233235
/** Prereqs, no specific format */
234236
prereqs: string;

0 commit comments

Comments
 (0)