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.
This commit is contained in:
@@ -163,3 +163,83 @@ describe('optimize (integration)', () => {
|
||||
expect(prioResult.achieved.length).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('externalCredits behavior', () => {
|
||||
it('hard 3-spec cap holds even with external credits', () => {
|
||||
// Even with 9 external HCR (a spec the courses don't otherwise support),
|
||||
// maximizeCount must never report more than 3 achieved.
|
||||
const result = maximizeCount(
|
||||
financeHeavyCourses,
|
||||
allSpecIds,
|
||||
[],
|
||||
undefined,
|
||||
{ HCR: 9 },
|
||||
);
|
||||
expect(result.achieved.length).toBeLessThanOrEqual(3);
|
||||
});
|
||||
|
||||
it('external credits can substitute into the 3-spec set', () => {
|
||||
// External 9 in HCR makes HCR feasible for free; the optimizer can pick
|
||||
// HCR as one of the 3 achieved specs.
|
||||
const result = maximizeCount(
|
||||
financeHeavyCourses,
|
||||
allSpecIds,
|
||||
[],
|
||||
undefined,
|
||||
{ HCR: 9 },
|
||||
);
|
||||
expect(result.achieved).toContain('HCR');
|
||||
});
|
||||
|
||||
it('missing_required precedence: external alone cannot achieve a gated spec', () => {
|
||||
// BRM requires fall4-brand-strategy. Don't include it; pin fall4-game-theory instead.
|
||||
const noBrandStrategy = financeHeavyCourses.filter((c) => c !== 'fall4-financial-services')
|
||||
.concat(['fall4-game-theory']);
|
||||
|
||||
const result = maximizeCount(
|
||||
noBrandStrategy,
|
||||
allSpecIds,
|
||||
[],
|
||||
undefined,
|
||||
{ BRM: 9 },
|
||||
);
|
||||
expect(result.achieved).not.toContain('BRM');
|
||||
|
||||
const statuses = determineStatuses(
|
||||
noBrandStrategy,
|
||||
[],
|
||||
result.achieved,
|
||||
undefined,
|
||||
{ BRM: 9 },
|
||||
);
|
||||
expect(statuses['BRM']).toBe('missing_required');
|
||||
});
|
||||
|
||||
it('priorityOrder honors external credits in achievability', () => {
|
||||
// Rank HCR first; without external it would be skipped, with 9 external it's first achieved
|
||||
const ranking = ['HCR', ...allSpecIds.filter((id) => id !== 'HCR')];
|
||||
const without = priorityOrder(financeHeavyCourses, ranking, []);
|
||||
expect(without.achieved).not.toContain('HCR');
|
||||
|
||||
const withExt = priorityOrder(
|
||||
financeHeavyCourses,
|
||||
ranking,
|
||||
[],
|
||||
undefined,
|
||||
{ HCR: 9 },
|
||||
);
|
||||
expect(withExt.achieved[0]).toBe('HCR');
|
||||
});
|
||||
|
||||
it('upper bounds reflect external credit additions', () => {
|
||||
const result = optimize(
|
||||
[],
|
||||
allSpecIds,
|
||||
[],
|
||||
'maximize-count',
|
||||
undefined,
|
||||
{ GLB: 5 },
|
||||
);
|
||||
expect(result.upperBounds['GLB']).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user