v1.1.0: Add cancelled course, duplicate prevention, and credit bar ticks

- Mark "Managing Growing Companies" as cancelled with visual indicator and solver exclusion
- Prevent selecting duplicate courses across elective sets (e.g., same course in Spring and Summer)
- Add 2.5-credit interval tick marks to specialization progress bars
- Bump version to 1.1.0 with date display in UI header
This commit is contained in:
2026-03-13 16:11:56 -04:00
parent 5c598d1fc6
commit 8b887f7750
14 changed files with 156 additions and 39 deletions

View File

@@ -56,8 +56,9 @@ export function maximizeCount(
selectedCourseIds: string[],
ranking: string[],
openSetIds: string[],
excludedCourseIds?: Set<string>,
): { achieved: string[]; allocations: Record<string, Record<string, number>> } {
const candidates = preFilterCandidates(selectedCourseIds, openSetIds);
const candidates = preFilterCandidates(selectedCourseIds, openSetIds, excludedCourseIds);
// Only check specs that can be achieved from selected courses alone (not open sets)
// Filter to candidates that have qualifying selected courses
@@ -108,8 +109,9 @@ export function priorityOrder(
selectedCourseIds: string[],
ranking: string[],
openSetIds: string[],
excludedCourseIds?: Set<string>,
): { achieved: string[]; allocations: Record<string, Record<string, number>> } {
const candidates = new Set(preFilterCandidates(selectedCourseIds, openSetIds));
const candidates = new Set(preFilterCandidates(selectedCourseIds, openSetIds, excludedCourseIds));
// Only consider specs that have qualifying selected courses
const withSelectedCourses = new Set(
@@ -145,11 +147,12 @@ export function determineStatuses(
selectedCourseIds: string[],
openSetIds: string[],
achieved: string[],
excludedCourseIds?: Set<string>,
): Record<string, SpecStatus> {
const achievedSet = new Set(achieved);
const selectedSet = new Set(selectedCourseIds);
const openSetSet = new Set(openSetIds);
const upperBounds = computeUpperBounds(selectedCourseIds, openSetIds);
const upperBounds = computeUpperBounds(selectedCourseIds, openSetIds, excludedCourseIds);
const statuses: Record<string, SpecStatus> = {};
for (const spec of SPECIALIZATIONS) {
@@ -189,11 +192,12 @@ export function optimize(
ranking: string[],
openSetIds: string[],
mode: 'maximize-count' | 'priority-order',
excludedCourseIds?: Set<string>,
): AllocationResult {
const fn = mode === 'maximize-count' ? maximizeCount : priorityOrder;
const { achieved, allocations } = fn(selectedCourseIds, ranking, openSetIds);
const statuses = determineStatuses(selectedCourseIds, openSetIds, achieved);
const upperBounds = computeUpperBounds(selectedCourseIds, openSetIds);
const { achieved, allocations } = fn(selectedCourseIds, ranking, openSetIds, excludedCourseIds);
const statuses = determineStatuses(selectedCourseIds, openSetIds, achieved, excludedCourseIds);
const upperBounds = computeUpperBounds(selectedCourseIds, openSetIds, excludedCourseIds);
return { achieved, allocations, statuses, upperBounds };
}