Initial commit: Converge portfolio rationalization app

Next.js app for mapping a customer's competitive tool landscape onto our
portfolio and building the consolidation business case: assessment
workbench, customer report and Word export, product catalog, competitive
knowledge base, dossier import, shareable landscape questionnaires with a
review inbox, tool-footprint and business-unit value analysis, and an
implementation timeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 11:48:42 -04:00
commit d5c77543e8
64 changed files with 30861 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
const B='http://127.0.0.1:3500';
const get=(u)=>fetch(B+u).then(r=>r.json());
const put=(u,b)=>fetch(B+u,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(b)}).then(r=>r.json());
let pass=0,fail=0; const t=(n,ok)=>{ok?(pass++,console.log(' PASS',n)):(fail++,console.log(' FAIL',n))};
const kb0 = await get('/api/knowledge-base');
t('seeded KB loads', kb0.entries.length >= 630);
const f5 = kb0.entries.find(e=>e.label==='F5 BIG-IP');
t('F5 BIG-IP pre-mapped to Avi', f5 && f5.productIds.includes('ans-avi'));
t('seeded entry has no observations', f5.observations.length === 0);
// Save the seeded assessment -> should learn from its 10 candidates.
const a = await get('/api/assessments/northwind-financial');
await put('/api/assessments/northwind-financial', a);
const kb1 = await get('/api/knowledge-base');
const learned = kb1.entries.filter(e=>e.observations.length>0);
t('learned from all 10 candidates', learned.length === 10);
const splunk = kb1.entries.find(e=>e.label==='Splunk Enterprise');
t('Splunk Enterprise captured', !!splunk);
t('Splunk learned our real mapping', splunk.productIds.join()==='vcf-ops-logs,aod-dx-oi');
t('Splunk learned switchEase 2', splunk.switchEase === 2);
t('Splunk learned savingsPct 45', splunk.savingsPct === 45);
t('Splunk learned risks text', /SOC dashboards/.test(splunk.risks));
t('observation links back to assessment', splunk.observations[0].assessmentId === 'northwind-financial');
// Re-saving must update in place, not stack duplicates.
await put('/api/assessments/northwind-financial', a);
const kb2 = await get('/api/knowledge-base');
const splunk2 = kb2.entries.find(e=>e.label==='Splunk Enterprise');
t('re-save does not duplicate observations', splunk2.observations.length === 1);
t('entry count stable after re-save', kb2.entries.length === kb1.entries.length);
// A pinned entry must survive learning untouched.
const pinTarget = structuredClone(kb2);
const p = pinTarget.entries.find(e=>e.label==='Splunk Enterprise');
p.pinned = true; p.savingsPct = 99; p.switchEase = 5;
await put('/api/knowledge-base', pinTarget);
await put('/api/assessments/northwind-financial', a);
const kb3 = await get('/api/knowledge-base');
const splunk3 = kb3.entries.find(e=>e.label==='Splunk Enterprise');
t('curated entry keeps savingsPct', splunk3.savingsPct === 99);
t('curated entry keeps switchEase', splunk3.switchEase === 5);
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail?1:0);