v1.2.2: Add Healthcare specialization, mark cancelled courses, rename Digital Marketing

Apply the J27 (5/6/2026) Stern specialization sheet:

- Add Healthcare (HCR) as the 15th specialization, with HCR cross-listings on
  spr2-health-medical, spr3-analytics-ml, sum2-social-media (renamed), and
  fall1-managing-change. 10 credits available, no required-course gate.
- Rename sum2-social-media to "Digital Marketing Strategy in Practice";
  replace its description with new MSKCC-anchored content; clear instructor
  pending confirmation of new lead.
- Switch from delete-and-replace to the previously-unused cancelled flag
  (Approach B): mark spr5-customer-insights cancelled, add Managing Growing
  Companies back to Summer Set 2 as a cancelled placeholder per the printed
  sheet.
- Update data integrity tests: course count 46 -> 47, spec count 14 -> 15;
  per-spec "across sets" helper now filters cancelled courses so future
  cancellations trigger an obvious assertion failure (BRM 6 -> 5,
  MKT 7 -> 6, HCR 4 new).
- Replace hardcoded 14 in optimizer.test.ts with SPECIALIZATIONS.length.
This commit is contained in:
2026-05-09 14:50:26 -04:00
parent 0beafb58b5
commit 4d6f81d1e5
14 changed files with 325 additions and 20 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-09
@@ -0,0 +1,3 @@
# j27-spec-update
Apply J27 specialization sheet (5/6/2026): add Healthcare specialization, mark Customer Insights cancelled, add cancelled Managing Growing Companies entry, rename and re-describe Social Media → Digital Marketing Strategy in Practice, add HCR qualifications to four existing courses.
@@ -0,0 +1,75 @@
## Context
The Stern J27 Specializations one-pager (5/6/2026) introduces structural changes that go beyond a simple data refresh:
- A new **Healthcare** specialization (HCR) — the first new specialization since the app's initial 14
- Two cancelled-course bookkeeping items (Customer Insights becomes cancelled; Managing Growing Companies reappears as a cancelled placeholder)
- A renamed elective (Social Media and Mobile Technology → Digital Marketing Strategy in Practice) with a substantively different syllabus, instructor lineup, and a new qualification (Healthcare)
The existing codebase already includes the full plumbing for `cancelled: true` (defined in `app/src/data/types.ts:22`, indexed in `app/src/data/lookups.ts:43-45`, excluded from solver via `app/src/state/appState.ts:94-95`, greyed-out in `app/src/components/CourseSelection.tsx:279`) but the prior change `replace-cancelled-course-with-innovation-design` chose hard-deletion over the flag. This change formalizes the flag as the standing pattern.
## Goals / Non-Goals
**Goals:**
- Bring data files into exact correspondence with the J27 sheet
- Establish `cancelled: true` as the canonical pattern for cancelled courses (Approach B)
- Make the data integrity test reflect *reachable* credits (excluding cancelled courses), so that a future cancellation triggers an obviously-failing reachability assertion
- Verify the published per-spec credit totals from the CSV match the computed reachability table
**Non-Goals:**
- No changes to the solver, lookups infrastructure, or React components — the cancelled flag is already honored end-to-end
- No new color/iconography work for HCR beyond what falls out of the existing rendering of unknown specializations (if a missing color is observed during browser testing, address with a follow-up; do not block on it)
- No data-migration support for older saved selections — the app does not persist user state across reloads
- No support for multiple simultaneous cohorts; this is an in-place J27 update
## Decisions
### Approach B (cancelled flag), not hard-delete
Use `cancelled: true` rather than removing entries. The flag exists, is fully wired, and matches the printed sheet 1:1 (which preserves the slot even when a course is cancelled). The prior delete-and-replace pattern was driven by lack of UI greying — that's no longer a constraint.
**Alternatives considered:** Hard-delete the cancelled entries, as in `replace-cancelled-course-with-innovation-design`. Rejected because the school's official sheet shows the cancelled slot, and preserving slot ordering helps students cross-reference printed materials.
### Add `sum2-managing-growing-companies` as a fresh cancelled entry
Even though a previous change removed this id, the J27 sheet reintroduces it as a cancelled placeholder. We add it back with `cancelled: true` and empty qualifications. The id is reusable because no persisted state references it.
**Alternative considered:** Skip it (Summer Set 2 stays at 4 entries). Rejected because the printed sheet shows 5 slots; hiding one would create a discrepancy the user might flag as a bug.
### Update reachability test to exclude cancelled courses
Modify `data.test.ts` so the per-spec "across sets" assertion filters out qualifications belonging to cancelled courses before counting distinct sets. This matches the CSV's published credit totals (BRM 12.5 = 5 sets × 2.5; MKT 15.0 = 6 sets × 2.5; HCR 10.0 = 4 sets × 2.5).
**Alternative considered:** Leave the test counting cancelled courses and accept that BRM/MKT reach-counts disagree with the published totals. Rejected — the test's stated purpose is to mirror the reachability table, and divergence makes regressions invisible.
### Do NOT change `coursesBySpec` to filter cancelled
The lookup map keeps cancelled qualifications. The solver already excludes them via `appState`'s `excluded` set, and other consumers (or future debug tooling) may want the full picture. Filter at the consumer (test), not at the source.
**Alternative considered:** Strip cancelled entries from `coursesBySpec` directly. Rejected because that hides data from any future caller that might legitimately want to inspect cancelled courses, and the test is the only consumer that currently cares.
### Clear instructor for Digital Marketing course
The new description (`digital-marketing.txt`) describes a substantively different course (MSKCC engagement, agentic AI) led by guest contributors from L'Oréal and Google Gemini, with no clear primary instructor named. Set `instructors: []` rather than carry forward Stewart Krentzman from the old syllabus, which would be misleading.
**Alternative considered:** Retain the prior instructor name. Rejected — the course content has changed materially, and showing a stale instructor is worse than showing none.
### Healthcare gets no required-course gate
The CSV asterisk convention (used for required courses) does not appear on any HCR-qualifying course. Healthcare therefore has no `requiredCourseId`.
## Risks / Trade-offs
- **Reachability test now depends on cancelled flag** → Mitigation: the test continues to assert all 14 (now 15) specs; if the cancelled flag is accidentally cleared, the test's set count for BRM/MKT will jump back up and fail
- **HCR may render with no themed color in legend** → Mitigation: verify in browser; if a default color is unacceptable, file a follow-up rather than blocking this change
- **Reusing the `sum2-managing-growing-companies` id** → Risk is low (no persistence); document the reuse in the changelog so a future bisecting developer is not surprised
- **CSV header dates the sheet 5/6/2026 (one day before today's date)** → No risk; just confirms the sheet is current
## Migration Plan
1. Land data changes in a single commit (or one commit per file, whichever fits the repo's PR style)
2. Run `pnpm test` (or `npm test`) and confirm all data integrity tests pass with updated expectations
3. Browser-verify: load the app, observe HCR appears in the specialization list, the two cancelled courses render greyed, the renamed Digital Marketing course shows the new description on info-popover hover
4. Bump `__APP_VERSION__` to `1.2.2` (patch bump consistent with prior data-only updates) and `__APP_VERSION_DATE__` in `app/vite.config.ts`
5. Add a `1.2.2` entry to `CHANGELOG.md`
Rollback: revert the data file changes; no schema or build changes to undo.
## Open Questions
- **Lead instructor for Digital Marketing Strategy in Practice** — the description names guest speakers but no primary instructor; clear for now and refresh if the school publishes one
- **HCR display color/order** — verify in browser; address with a follow-up if the default rendering looks wrong
@@ -0,0 +1,37 @@
## Why
The Stern School published an updated J27 Specializations one-pager (dated 5/6/2026) that introduces a new Healthcare specialization, cancels the Customer Insights elective, lists Managing Growing Companies as a cancelled placeholder in Summer Set 2, renames Social Media and Mobile Technology to Digital Marketing Strategy in Practice (with a substantively different course description and focus), and cross-lists four existing courses to the new Healthcare specialization. The solver's data files must match the official sheet so students rely on accurate availability information.
## What Changes
- Add **Healthcare (HCR)** specialization (10 credits, no required course)
- Add HCR qualification to four existing courses:
- `spr2-health-medical` (Business of Health & Medical Care)
- `spr3-analytics-ml` (Analytics & Machine Learning for Managers)
- `sum2-social-media` (formerly Social Media and Mobile Technology)
- `fall1-managing-change` (Managing Change)
- Rename `sum2-social-media` to **Digital Marketing Strategy in Practice** and replace its course description with the new MSKCC-anchored content (provided in `digital-marketing.txt`); clear instructor field pending confirmation of new lead instructor
- Mark **`spr5-customer-insights`** as `cancelled: true` (retain entry; do not delete) — switching from prior delete-and-replace pattern to the existing-but-unused `cancelled` flag pattern (Approach B)
- Add a new cancelled-only entry **`sum2-managing-growing-companies`** ("Managing Growing Companies") to Summer Set 2 with `cancelled: true` and no qualifications, restoring the printed sheet's 5-slot listing
- Update test assertions in `app/src/data/__tests__/data.test.ts` to reflect the 15th specialization, the 5-entry sum2 set, and revised per-spec set counts (BRM, MKT lose `spr5-customer-insights`)
## Capabilities
### New Capabilities
_None — this is a data refresh that uses existing capabilities (`course-data`, `specialization-ranking`)._
### Modified Capabilities
- `course-data`: Add Healthcare specialization, four HCR cross-listings, the renamed Digital Marketing course, and two cancelled-flag entries. Adopt `cancelled: true` (Approach B) as the standing pattern for cancelled courses, superseding the prior delete-and-replace approach.
## Impact
- `app/src/data/specializations.ts` — add HCR entry
- `app/src/data/courses.ts` — five qualification/name edits, one new cancelled entry, one cancelled-flag edit
- `app/src/data/electiveSets.ts` — append `sum2-managing-growing-companies` to `sum2.courseIds`
- `app/src/data/courseDescriptions.ts` — replace `sum2-social-media` description; clear instructors
- `app/src/data/__tests__/data.test.ts` — update specialization count (14→15), course count (46→47), and per-spec/STR-marker assertions
- `app/vite.config.ts` — bump `__APP_VERSION__` and `__APP_VERSION_DATE__`
- `CHANGELOG.md` — add release entry
- No code changes to solver, lookups, or UI: existing `cancelled` plumbing in `lookups.ts:43-50`, `appState.ts:94-95`, and `CourseSelection.tsx:279` already handles greying-out and solver exclusion
@@ -0,0 +1,117 @@
## ADDED Requirements
### Requirement: Healthcare specialization
The system SHALL include "Healthcare" (abbreviation `HCR`) as a defined specialization with no required course gate.
#### Scenario: Healthcare appears in specialization list
- **WHEN** the data module is loaded
- **THEN** a specialization with id `HCR`, name "Healthcare", and abbreviation `HCR` is present in `SPECIALIZATIONS`
#### Scenario: Healthcare has no required course
- **WHEN** inspecting the Healthcare specialization
- **THEN** its `requiredCourseId` is undefined
#### Scenario: Healthcare reaches four elective sets
- **WHEN** counting the distinct elective sets containing at least one non-cancelled HCR-qualifying course
- **THEN** the count is exactly 4 (`spr2`, `spr3`, `sum2`, `fall1`), yielding 10.0 credits available (4 × 2.5)
### Requirement: Healthcare cross-listings on existing courses
The system SHALL add Healthcare (`HCR`, standard marker) as an additional qualification on four existing courses without removing their prior qualifications.
#### Scenario: Business of Health & Medical Care qualifies for Healthcare
- **WHEN** inspecting `spr2-health-medical` qualifications
- **THEN** the list includes both `STR` (S2) and `HCR` (standard)
#### Scenario: Analytics & Machine Learning for Managers qualifies for Healthcare
- **WHEN** inspecting `spr3-analytics-ml` qualifications
- **THEN** the list includes both `MTO` (standard) and `HCR` (standard)
#### Scenario: Digital Marketing Strategy in Practice qualifies for Healthcare
- **WHEN** inspecting `sum2-social-media` qualifications
- **THEN** the list includes `BRM`, `EMT`, `MKT`, and `HCR` (all standard)
#### Scenario: Managing Change qualifies for Healthcare
- **WHEN** inspecting `fall1-managing-change` qualifications
- **THEN** the list includes `LCM`, `MGT`, `STR` (S2), and `HCR` (standard)
### Requirement: Cancelled courses preserved with cancelled flag
The system SHALL retain cancelled course entries in the course data with `cancelled: true` rather than deleting them. Cancelled courses SHALL NOT be considered selectable, SHALL be excluded from solver computations, and SHALL render in the UI as visibly disabled.
#### Scenario: Customer Insights is marked cancelled
- **WHEN** inspecting `spr5-customer-insights`
- **THEN** the course has `cancelled: true` and remains a member of `spr5`
#### Scenario: Managing Growing Companies present as cancelled placeholder in Summer Set 2
- **WHEN** inspecting Summer Elective Set 2 (`sum2`)
- **THEN** it contains exactly five course entries, one of which is `sum2-managing-growing-companies` with `cancelled: true` and an empty qualifications list
#### Scenario: Cancelled courses excluded from solver
- **WHEN** the solver computes specialization assignments
- **THEN** no cancelled course id contributes credits toward any specialization
## MODIFIED Requirements
### Requirement: Course definitions
The system SHALL define exactly 47 courses. Each course SHALL have an ID, display name, and the ID of the elective set it belongs to. Courses MAY carry a `cancelled: true` flag indicating the offering has been withdrawn.
#### Scenario: Course count
- **WHEN** the data module is loaded
- **THEN** exactly 47 courses are defined
#### Scenario: Each course belongs to one set
- **WHEN** iterating all courses
- **THEN** every course references a valid elective set ID, and the set's course list includes that course
#### Scenario: Cancelled courses are flagged, not deleted
- **WHEN** filtering courses by `cancelled === true`
- **THEN** the result includes both `spr5-customer-insights` and `sum2-managing-growing-companies`
### Requirement: Specialization definitions
The system SHALL define exactly 15 specializations. Each specialization SHALL have an ID, display name, and abbreviation. Specializations with a required course gate SHALL reference the required course ID.
#### Scenario: Specialization count
- **WHEN** the data module is loaded
- **THEN** exactly 15 specializations are defined
#### Scenario: Healthcare is included
- **WHEN** searching specializations for id `HCR`
- **THEN** a specialization named "Healthcare" is found
#### Scenario: Required course mappings unchanged
- **WHEN** inspecting specializations with required courses
- **THEN** exactly 4 specializations have required course gates: SBI → `spr4-sustainability`, ENT → `spr4-foundations-entrepreneurship`, EMT → `sum3-entertainment-media`, BRM → `fall4-brand-strategy`
### Requirement: Course-specialization qualification matrix
Each course SHALL declare which specializations it qualifies for, with a marker type of standard (■), S1, or S2. Courses with no qualifying specializations (including cancelled courses) SHALL have an empty qualification list. Reachability counts SHALL exclude qualifications belonging to courses flagged `cancelled`.
#### Scenario: Marker types
- **WHEN** inspecting course qualifications
- **THEN** every qualification entry uses one of three marker types: standard, S1, or S2
#### Scenario: Strategy markers
- **WHEN** counting Strategy-qualifying courses across all (non-cancelled) courses
- **THEN** exactly 9 courses have S1 markers and exactly 8 courses have S2 markers
#### Scenario: Reachable distinct-set counts per specialization
- **WHEN** counting distinct elective sets containing at least one non-cancelled course qualifying for each specialization
- **THEN** the counts are: MGT 11, STR 9, LCM 9, FIN 9, CRF 8, MKT 6, BNK 6, BRM 5, FIM 6, MTO 6, GLB 5, EMT 4, ENT 4, SBI 4, HCR 4
- **AND** these counts represent raw distinct-set reachability, not the CSV's published credit totals (which additionally apply S1/S2 and shared-course rules — out of scope for this assertion)
### Requirement: Renamed Digital Marketing course
The system SHALL display course `sum2-social-media` with the name "Digital Marketing Strategy in Practice" and the updated description anchored on the Memorial Sloan Kettering Cancer Center engagement. The course id SHALL remain `sum2-social-media` (unchanged) so existing lookup tables and tests continue to resolve.
#### Scenario: Display name updated
- **WHEN** inspecting the course with id `sum2-social-media`
- **THEN** its `name` is "Digital Marketing Strategy in Practice"
#### Scenario: Description reflects MSKCC engagement
- **WHEN** the user opens the course info popover for `sum2-social-media`
- **THEN** the description references Memorial Sloan Kettering Cancer Center, agentic AI, and digital strategy practice
#### Scenario: Instructor cleared pending confirmation
- **WHEN** inspecting the course's instructor list in `COURSE_DESCRIPTIONS`
- **THEN** the `instructors` array is empty
## REMOVED Requirements
_None. The "Managing Growing Companies" id, previously removed by `replace-cancelled-course-with-innovation-design`, is reintroduced as a cancelled placeholder per the J27 sheet — see ADDED Requirements above._
+52
View File
@@ -0,0 +1,52 @@
## 1. Specialization Data
- [x] 1.1 Add `{ id: 'HCR', name: 'Healthcare', abbreviation: 'HCR' }` to `SPECIALIZATIONS` in `app/src/data/specializations.ts` (no `requiredCourseId`)
## 2. Course Data
- [x] 2.1 Add `{ specId: 'HCR', marker: 'standard' }` to qualifications of `spr2-health-medical` in `app/src/data/courses.ts`
- [x] 2.2 Add `{ specId: 'HCR', marker: 'standard' }` to qualifications of `spr3-analytics-ml`
- [x] 2.3 Rename `sum2-social-media` `name` to `'Digital Marketing Strategy in Practice'` and add `{ specId: 'HCR', marker: 'standard' }` to its qualifications
- [x] 2.4 Add `{ specId: 'HCR', marker: 'standard' }` to qualifications of `fall1-managing-change`
- [x] 2.5 Add `cancelled: true` to `spr5-customer-insights`; leave its `qualifications` array unchanged
- [x] 2.6 Append a new course entry `{ id: 'sum2-managing-growing-companies', name: 'Managing Growing Companies', setId: 'sum2', qualifications: [], cancelled: true }` to `COURSES`
## 3. Elective Set Membership
- [x] 3.1 In `app/src/data/electiveSets.ts`, append `'sum2-managing-growing-companies'` to `sum2.courseIds` (placement: end of list, matching the order of the printed J27 sheet)
## 4. Course Description
- [x] 4.1 In `app/src/data/courseDescriptions.ts`, replace the value at key `'sum2-social-media'` with the description text from `digital-marketing.txt` (paragraphs joined with `\n\n`); set `instructors: []`
## 5. Lookup Behavior Verification
- [x] 5.1 Confirm `coursesBySpec` in `app/src/data/lookups.ts` continues to include cancelled-course qualifications (no code change required; the test asserts the consumer-side filter, not the lookup itself)
## 6. Test Updates
- [x] 6.1 Update `app/src/data/__tests__/data.test.ts`: change `expect(COURSES.length).toBe(46)` to `47`
- [x] 6.2 Change `expect(SPECIALIZATIONS.length).toBe(14)` to `15`
- [x] 6.3 In the "across sets" describe block, change the helper to filter out qualifications belonging to cancelled courses before counting distinct sets (read `cancelled` via `COURSES.find(...)`)
- [x] 6.4 Update `expectedAcrossSets` map to: MGT 11, STR 9, LCM 9, FIN 9, CRF 8, MKT **6** (was 7), BNK 6, BRM **5** (was 6), FIM 6, MTO 6, GLB 5, EMT 4, ENT 4, SBI 4, **HCR 4** (new). Only BRM and MKT decrement (cancelled `spr5-customer-insights` removes spr5 from both); HCR is added. All other values unchanged. Note: this test counts raw distinct sets, not CSV-published credit totals (which apply S1/S2 and shared-course rules); do not "correct" other values to match the CSV.
- [x] 6.5 Confirm STR S1=9 / S2=8 marker counts test still passes unchanged (no STR markers added or removed)
- [x] 6.6 Run `npm test --prefix app` (or the project's preferred command) and confirm all data tests pass
## 7. Browser Verification
- [x] 7.1 Start dev server (`npm run dev --prefix app`) and load the app
- [x] 7.2 Confirm "Healthcare" appears in the specialization list/legend
- [x] 7.3 Confirm `spr5-customer-insights` ("Customer Insights") renders greyed/disabled in Spring Set 5
- [x] 7.4 Confirm `sum2-managing-growing-companies` ("Managing Growing Companies") renders greyed/disabled as the 5th entry in Summer Set 2
- [x] 7.5 Confirm `sum2-social-media` displays as "Digital Marketing Strategy in Practice"; open its info popover and verify the new MSKCC-anchored description appears with no instructor listed
- [x] 7.6 Pick a Healthcare-qualifying course (e.g., Managing Change), pin it, and confirm Healthcare appears in the resulting specialization analysis
- [x] 7.7 Note any rendering issue with HCR (missing color/legend entry) and capture for follow-up if found
## 8. Version and Changelog
- [x] 8.1 Bump `__APP_VERSION__` to `1.2.2` and update `__APP_VERSION_DATE__` to today's date in `app/vite.config.ts`
- [x] 8.2 Add a `## [1.2.2]` entry to `CHANGELOG.md` describing: new Healthcare specialization, four HCR cross-listings, Digital Marketing Strategy in Practice rename + new description, two cancelled-course entries (Customer Insights, Managing Growing Companies), and adoption of the `cancelled` flag pattern
## 9. Cleanup
- [x] 9.1 Delete `J27 Specializations.csv` and `digital-marketing.txt` from the repo root (these were drop-in inputs; data has been migrated into source)