// Phase 4 SSOT smoke test // Validates: // 1. parser.js interview1/2 PassRate uses decidedFirst // 2. metrics-definitions.js isCorporateRole uses METRIC_RULES // 3. simulation.js _suggestExcludedHires uses METRIC_RULES // // NOTE: We do not load all files into vm (too many deps). We use static // string inspection for key SSOT invariants, plus ad-hoc execution for // metrics-definitions.js and parser.js aggP formula equivalence. const fs = require('fs'); const base = '/sessions/awesome-practical-goldberg/mnt/Documents/qolony-hr-dashboard-ver11/js'; function mustContain(path, pattern, label) { const txt = fs.readFileSync(path, 'utf8'); if (!txt.match(pattern)) { console.error(`❌ FAIL ${label}: pattern ${pattern} not found in ${path}`); process.exitCode = 1; return false; } console.log(`✓ ${label}`); return true; } function mustNotContain(path, pattern, label) { const txt = fs.readFileSync(path, 'utf8'); if (txt.match(pattern)) { console.error(`❌ FAIL ${label}: disallowed pattern ${pattern} still present in ${path}`); process.exitCode = 1; return false; } console.log(`✓ ${label}`); return true; } console.log('\n=== parser.js SSOT §3.2 decidedFirst ==='); mustContain(`${base}/parser.js`, /interview1PassRate:\s*pc\(o\.interview2\.length,\s*o\.interview1Done\.length\s*>\s*0\s*\?\s*o\.interview1Done\.length\s*:\s*o\.docPass\.length\)/, 'parser.js: interview1PassRate uses decidedFirst (denom=interview1Done || docPass)'); mustContain(`${base}/parser.js`, /interview2PassRate:\s*pc\(o\.interview2\.filter\(r\s*=>\s*r\._i3\)\.length,\s*o\.interview2Done\.length\s*>\s*0\s*\?\s*o\.interview2Done\.length\s*:\s*o\.interview2\.length\)/, 'parser.js: interview2PassRate uses decidedFirst (num=_i3 count, denom=interview2Done || interview2)'); mustNotContain(`${base}/parser.js`, /r\.interview2PassRate\s*=\s*pc\(r\.naitei,\s*r\.interview2\)/, 'parser.js: aggM interview2PassRate override removed (now inherits aggP decidedFirst)'); console.log('\n=== simulation.js SSOT §2.2 corporate exclusion ==='); mustContain(`${base}/simulation.js`, /METRIC_RULES\.corporateExclusion\.departments/, 'simulation.js: uses METRIC_RULES.corporateExclusion.departments'); mustNotContain(`${base}/simulation.js`, /backofficeKeywords\s*=\s*\['管理本部',\s*'経営推進室'\]/, 'simulation.js: hardcoded backofficeKeywords removed'); console.log('\n=== copilot.js SSOT §3.2 / §3.1 / §2.2 ==='); mustContain(`${base}/copilot.js`, /interview1PassRate\s*=\s*\(\(dAll\.interview1PassRate/, 'copilot.js: interview1PassRate uses dAll.* (no independent recalc)'); mustContain(`${base}/copilot.js`, /interview2PassRate\s*=\s*\(\(dAll\.interview2PassRate/, 'copilot.js: interview2PassRate uses dAll.* (no independent recalc)'); mustContain(`${base}/copilot.js`, /MetricsDefs\.isCorporateRole/, 'copilot.js: channel analysis uses MetricsDefs.isCorporateRole'); mustContain(`${base}/copilot.js`, /apps_effective/, 'copilot.js: uses apps_effective (tF)'); console.log('\n=== tasks.js SSOT §3.1 tR/tF ==='); mustContain(`${base}/tasks.js`, /apps_raw/, 'tasks.js: uses apps_raw (tR)'); mustContain(`${base}/tasks.js`, /apps_effective/, 'tasks.js: uses apps_effective (tF)'); console.log('\n=== metrics-definitions.js SSOT §2.2 corp exclusion ==='); mustContain(`${base}/metrics-definitions.js`, /METRIC_RULES\.corporateExclusion\.departments/, 'metrics-definitions.js: isCorporateRole uses METRIC_RULES.corporateExclusion.departments'); console.log('\n=== End-to-end logic test for MetricsDefs ==='); // Minimal vm test of metrics-definitions + config const vm = require('vm'); const ctx = { console, module: { exports: {} }, exports: {}, window: {} }; vm.createContext(ctx); try { vm.runInContext(fs.readFileSync(`${base}/config.js`, 'utf8'), ctx, { filename: 'config.js' }); } catch (e) { console.log('(config.js localStorage warning expected, continuing)'); } vm.runInContext(fs.readFileSync(`${base}/metrics-definitions.js`, 'utf8'), ctx, { filename: 'metrics-definitions.js' }); const M = ctx.MetricsDefs || ctx.window.MetricsDefs; const tests = [ { name: 'HR戦略推進室 マネージャー', expect: true }, { name: '上場準備室 アナリスト', expect: true }, { name: '内部監査室 監査マネージャー', expect: true }, { name: '経営推進室 執行役員', expect: true }, { name: '経営企画室 シニア', expect: true }, { name: 'ST 戦略コンサルタント', expect: false }, { name: 'EA エンタープライズ', expect: false }, ]; tests.forEach(t => { const got = M.isCorporateRole({ '求人名': t.name }); const ok = got === t.expect; if (!ok) { process.exitCode = 1; } console.log(`${ok ? '✓' : '❌'} isCorporateRole("${t.name}") = ${got} (expected ${t.expect})`); }); console.log('\n=== evaluateParallelRate §4.3 (5-25% range) ==='); const pTests = [ { v: 0.03, expect: 'low' }, { v: 0.05, expect: 'ok' }, { v: 0.15, expect: 'ok' }, { v: 0.25, expect: 'ok' }, { v: 0.26, expect: 'high' }, { v: 0.40, expect: 'high' }, ]; pTests.forEach(t => { const got = M.evaluateParallelRate(t.v); const ok = got === t.expect; if (!ok) process.exitCode = 1; console.log(`${ok ? '✓' : '❌'} evaluateParallelRate(${t.v}) = ${got} (expected ${t.expect})`); }); console.log('\n' + (process.exitCode ? '❌ Some checks FAILED' : '✅ All checks passed'));