Files
emba-course-solver/openspec/changes/add-external-credits/proposal.md
T
Bill 3a5ebaa17a v1.5.0: External credits per specialization
Students can now record credits earned in courses taken outside the J27
program via an inline editable amber chip on each spec card. Values flow
through the LP (per-spec demand reduces by external amount), upper-bound
math, decision-tree search, and the credit bar visualization. The 9-credit
threshold and the 3-spec achievement cap are unchanged; required-course
gates remain authoritative — external credits never satisfy them.
2026-05-10 11:47:22 -04:00

40 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Why
Students sometimes earn credits in courses taken outside the J27 program (cross-registration, transfer, etc.) that the registrar will count toward a specialization. The current solver has no way to represent these credits, so its achievement and reachability claims understate what the student actually has. We want a lightweight escape hatch that lets a student dial in external credits per specialization and have the optimizer, status determination, and credit-bar visualization reflect them.
## What Changes
- New `externalCredits: Record<string, number>` field on `AppState` (specId → credits, default 0). Persisted to localStorage with the rest of the state. Edited via per-spec inline chip on the spec card.
- LP feasibility (`checkFeasibility`) reduces each spec's demand from `≥ 9` to `≥ max(0, 9 external[spec])`. Specs whose external credits already meet the threshold drop out of the LP entirely.
- Upper-bound and pre-filter math (`computeUpperBounds`, `preFilterCandidates`) add `external[spec]` to each spec's potential.
- The hard 3-spec cap in `maximizeCount` and `priorityOrder` is **retained** (program policy, not a math consequence). External credits may free in-program credits or substitute for an in-program spec within that cap, but never raise the cap above 3.
- "Achieved" coloring switches when `allocated + external ≥ 9` (was `allocated ≥ threshold`).
- Credit bar gets a new amber `#f59e0b` segment that fills from the left, before the existing in-program allocated/potential stripes.
- Allocation breakdown gains an `External` line item when `external[spec] > 0`.
- Required-course gates (BRM/EMT/ENT/SBI) are unchanged — external credits never satisfy them. A spec with sufficient external credits but a missing required course stays in `missing_required`.
- Leaf-cache invalidation in `useAppState` extends to `externalCredits` (treated like `ranking`/`mode`).
## Capabilities
### New Capabilities
_None._
### Modified Capabilities
- `optimization-engine`: external-credit-aware demand, upper-bound, candidate pre-filter, achievement ceiling, and cache invalidation. The LP shape itself (variables, capacity constraints, S2 enumeration) is unchanged.
- `unified-specialization-panel`: per-spec inline editable external-credits chip, amber bar segment, "achieved" coloring keyed off combined credit, External line in allocation breakdown.
## Impact
- `app/src/data/types.ts` — extend `AppState` shape (via `state/appState.ts`); no change to `Course`/`Specialization`/`AllocationResult`.
- `app/src/state/appState.ts` — add `externalCredits` to state, reducer actions (`setExternalCredits` or similar), localStorage load/save, and leaf-cache invalidation signature. Thread the value through to `optimize` and the worker.
- `app/src/solver/feasibility.ts` — new optional `externalCredits` parameter to `checkFeasibility`, `computeUpperBounds`, and `preFilterCandidates`; reduce demand and add to bounds.
- `app/src/solver/optimizer.ts` — thread `externalCredits` through `maximizeCount`, `priorityOrder`, `determineStatuses`, and `optimize`. The hardcoded 3-spec cap stays. Status determination keeps `missing_required` when applicable regardless of external totals.
- `app/src/solver/decisionTree.ts` + `app/src/workers/decisionTree.worker.ts` — accept `externalCredits` in the worker request and propagate to feasibility/upper-bound calls during search.
- `app/src/components/SpecializationRanking.tsx``CreditBar` accepts `external` and renders the amber segment as the leftmost stripe; `AllocationBreakdown` shows an `External` line; spec card adds an inline editable credits chip.
- `app/src/solver/__tests__/` — add LP tests for the demand reduction, upper-bound boost, and the lifted ceiling. Add tests verifying `missing_required` survives external-only achievement.
- `app/vite.config.ts` — version bump.
- `CHANGELOG.md` — release entry.
- No data-file (`data/courses.ts`, `data/specializations.ts`) changes.