3a5ebaa17a
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.
40 lines
3.9 KiB
Markdown
40 lines
3.9 KiB
Markdown
## 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.
|