Compare commits
2 Commits
1907e266c1
...
8b88402ecd
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b88402ecd | |||
| 578c87d59d |
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## v1.2.1 — 2026-03-27
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Achievable status accuracy** — specializations marked "Achievable" are now verified via LP feasibility check against already-achieved specs; previously, a specialization could show "Achievable" based on raw credit potential while actually being infeasible due to credit sharing with higher-priority achieved specializations
|
||||
|
||||
## v1.2.0 — 2026-03-27
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -109,6 +109,37 @@ describe('determineStatuses', () => {
|
||||
// Most specs only have 1 qualifying course in spr1 (2.5 credits < 9)
|
||||
expect(statuses['FIN']).toBe('unreachable');
|
||||
});
|
||||
|
||||
it('marks spec as unreachable when infeasible alongside achieved specs due to credit sharing', () => {
|
||||
// Bug scenario: CRF+STR achieved, LCM has 10 credit upper bound but
|
||||
// shared courses (spr3, fall3) are consumed by CRF/STR
|
||||
const selectedCourses = [
|
||||
'spr1-collaboration', // LCM, MGT
|
||||
'spr2-financial-services', // BNK, CRF, FIN, FIM
|
||||
'spr3-mergers-acquisitions', // CRF, FIN, LCM, STR(S1)
|
||||
'spr4-foundations-entrepreneurship', // ENT, MGT, STR(S1)
|
||||
'spr5-corporate-finance', // CRF, FIN
|
||||
'sum1-global-immersion', // GLB
|
||||
'sum2-business-drivers', // STR(S1)
|
||||
'sum3-valuation', // BNK, CRF, FIN, FIM
|
||||
'fall1-managing-change', // LCM, MGT, STR(S2)
|
||||
'fall2-decision-models', // MGT, MTO
|
||||
'fall3-corporate-governance', // LCM, MGT, SBI, STR(S1)
|
||||
'fall4-game-theory', // MGT, STR(S1)
|
||||
];
|
||||
|
||||
// Baseline: LCM is achievable when no specs are achieved (upper bound alone)
|
||||
const statusesBaseline = determineStatuses(selectedCourses, [], []);
|
||||
expect(statusesBaseline['LCM']).toBe('achievable');
|
||||
|
||||
// Core bug scenario: CRF+STR achieved (without MGT), LCM should still be unreachable
|
||||
const statusesWithoutMgt = determineStatuses(selectedCourses, [], ['CRF', 'STR']);
|
||||
expect(statusesWithoutMgt['LCM']).toBe('unreachable');
|
||||
|
||||
// LCM upper bound is 10 (>= 9) but infeasible alongside CRF+STR+MGT
|
||||
const statusesWithMgt = determineStatuses(selectedCourses, [], ['CRF', 'STR', 'MGT']);
|
||||
expect(statusesWithMgt['LCM']).toBe('unreachable');
|
||||
});
|
||||
});
|
||||
|
||||
describe('optimize (integration)', () => {
|
||||
|
||||
@@ -178,7 +178,20 @@ export function determineStatuses(
|
||||
continue;
|
||||
}
|
||||
|
||||
statuses[spec.id] = 'achievable';
|
||||
// Verify spec is actually feasible alongside already-achieved specs,
|
||||
// but only when all course slots are committed (no open sets remain).
|
||||
// With open sets, the user can still pick different courses, so LP
|
||||
// feasibility over selected courses alone would give false negatives.
|
||||
if (openSetSet.size === 0) {
|
||||
const testSet = [...achieved, spec.id];
|
||||
const filteredCourseIds = excludedCourseIds
|
||||
? selectedCourseIds.filter((id) => !excludedCourseIds.has(id))
|
||||
: selectedCourseIds;
|
||||
const feasResult = checkWithS2(filteredCourseIds, testSet);
|
||||
statuses[spec.id] = feasResult.feasible ? 'achievable' : 'unreachable';
|
||||
} else {
|
||||
statuses[spec.id] = 'achievable';
|
||||
}
|
||||
}
|
||||
|
||||
return statuses;
|
||||
|
||||
@@ -6,7 +6,7 @@ import react from '@vitejs/plugin-react'
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify('1.2.0'),
|
||||
__APP_VERSION__: JSON.stringify('1.2.1'),
|
||||
__APP_VERSION_DATE__: JSON.stringify('2026-03-27'),
|
||||
},
|
||||
server: {
|
||||
|
||||
Reference in New Issue
Block a user