## ADDED Requirements ### Requirement: External credits as per-spec input The application SHALL accept a per-specialization external credit value, expressed as a non-negative number. External credits represent credits earned in courses taken outside the J27 program that the student asserts toward a specialization. The values SHALL be stored in `AppState.externalCredits` as `Record` keyed by specialization id. Missing keys SHALL be treated as `0`. The values SHALL be persisted to localStorage alongside the rest of `AppState`. #### Scenario: External credits default to zero - **WHEN** a specialization has no entry in `externalCredits` - **THEN** the application SHALL treat its external credits as `0` everywhere (LP demand, upper bounds, bar visualization) #### Scenario: External credits persist across reload - **WHEN** the user enters an external credit value and reloads the page - **THEN** the value SHALL be restored from localStorage and applied to the LP and the bar #### Scenario: Negative or non-numeric input is rejected - **WHEN** the user attempts to commit a negative number, NaN, or empty string as an external credit value - **THEN** the value SHALL clamp to `0` (treated as no entry) ### Requirement: External credits reduce LP demand The LP feasibility checker SHALL reduce each specialization's demand from `≥ 9` to `≥ max(0, 9 − external[spec])`. Specializations whose external credits already meet or exceed the 9-credit threshold SHALL be omitted from the LP entirely (no `need_` constraint, no `x__` variables for that spec) while still being counted as achievable in the optimizer's output. #### Scenario: Partial external coverage reduces required in-program credits - **WHEN** a specialization has 4.0 external credits and 5.0 in-program credits available from the student's selections - **THEN** the LP SHALL find the spec feasible with `need_` set to `≥ 5` #### Scenario: External alone meets threshold - **WHEN** a specialization has 9.0 or more external credits - **THEN** the LP SHALL omit that spec's demand constraint and any related variables, and the optimizer SHALL include the spec in the achieved set without consuming any in-program credits #### Scenario: No external credits preserves prior behavior - **WHEN** every specialization's external credit value is `0` - **THEN** the LP SHALL produce the same constraints, variables, and feasibility verdict as before the change ### Requirement: External credits raise upper-bound and pre-filter potentials `computeUpperBounds` and `preFilterCandidates` SHALL add `external[spec]` to each specialization's potential credit total. A specialization SHALL pass the pre-filter if `(in-program potential + external) ≥ 9`. #### Scenario: External credits unlock previously-unreachable spec - **WHEN** a specialization's in-program potential is 6.0 (below the 9-credit threshold) but the student has 5.0 external credits in it - **THEN** the spec SHALL pass `preFilterCandidates` and SHALL receive an upper bound of `11.0` #### Scenario: External credits do not exceed reasonable bounds - **WHEN** external credits are added on top of the in-program upper bound - **THEN** the resulting upper bound SHALL be the simple sum (no cap), reflecting the truthful credit total ### Requirement: Required-course gates remain authoritative A specialization with a `requiredCourseId` SHALL retain `missing_required` status whenever the required course is neither selected nor available in an open elective set, regardless of the external credit total. External credits SHALL NOT advance the status of such a specialization to `achieved` or `achievable`. #### Scenario: External credits cannot satisfy a required course gate - **WHEN** the BRM specialization has 9.0 external credits but Brand Strategy is not selected and is in a pinned set holding a different course - **THEN** BRM's status SHALL remain `missing_required` - **AND** BRM SHALL NOT be counted in the achieved set #### Scenario: Required course gate becomes satisfiable - **WHEN** the required course is in an open elective set - **THEN** the spec MAY transition to `achievable` once the LP-with-external-credits confirms feasibility, following the same rules as without external credits ### Requirement: 3-spec achievement cap is policy, not just budget `maximizeCount` and `priorityOrder` SHALL cap the achieved set at 3 specializations regardless of external credit totals. External credits MAY shift which 3 specs are selected (e.g., admitting a spec that has no in-program qualifying courses, or freeing in-program credits for a different combination), but SHALL NOT raise the count above 3. #### Scenario: Hard cap holds without external credits - **WHEN** all `external[spec]` values are `0` - **THEN** `maximizeCount` SHALL never return a subset larger than 3 #### Scenario: Hard cap holds with sufficient external credits - **WHEN** the student has 9 or more external credits in a spec that the in-program courses do not naturally support - **THEN** the optimizer MAY include that spec in the achieved set in place of one it would otherwise pick, but `maximizeCount` SHALL never return a subset larger than 3 #### Scenario: priorityOrder respects the cap - **WHEN** the student has external credits sufficient to make 4 or more specs feasible - **THEN** `priorityOrder` SHALL stop adding specs to the achieved set after the third ### Requirement: Leaf cache invalidates on external-credit change The leaf cache in `useAppState` SHALL be cleared when any value in `externalCredits` changes (treated identically to a `ranking` or `mode` change). The cache invalidation signature SHALL include a deterministic stringification of `externalCredits` (e.g., sorted JSON of non-zero entries). #### Scenario: Editing an external credit value clears the cache - **WHEN** the user changes the external credit value for any specialization - **THEN** the leaf cache SHALL be emptied and the next search SHALL run as a full recomputation #### Scenario: No-op edit does not clear cache - **WHEN** the user opens the chip input and commits the same value that was already there - **THEN** the cache SHALL be retained (the signature is unchanged) ### Requirement: External credits propagate through the worker contract The `WorkerRequest` SHALL include `externalCredits: Record`. The decision-tree worker SHALL pass this value through to `searchDecisionTree`, which SHALL thread it into all `optimize`, `checkFeasibility`, `computeUpperBounds`, and `preFilterCandidates` calls used during the search. #### Scenario: Worker uses external credits during exhaustive search - **WHEN** the worker performs an exhaustive search with non-zero external credits - **THEN** every leaf's `PlanOutcome.achievedSpecs` SHALL reflect the external-credit-aware feasibility verdict #### Scenario: Empty external credits behaves like prior worker - **WHEN** the worker receives `externalCredits: {}` (or all-zero values) - **THEN** the worker's behavior and outputs SHALL match the prior implementation