87 lines
2.6 KiB
JavaScript
87 lines
2.6 KiB
JavaScript
// PAIRS: indices into VARS array [0=안정성, 1=수익_가능성, 2=자금_효율성, 3=사업_계획_완성도, 4=사회적_가치]
|
|
const AHP_PAIRS = [
|
|
[0,1],[0,2],[0,3],[0,4],
|
|
[1,2],[1,3],[1,4],
|
|
[2,3],[2,4],
|
|
[3,4]
|
|
];
|
|
|
|
function buildMatrix(pairwiseResults) {
|
|
const n = 5;
|
|
const matrix = Array.from({length: n}, () => Array(n).fill(1));
|
|
AHP_PAIRS.forEach(([i, j], idx) => {
|
|
const val = pairwiseResults[idx];
|
|
if (val >= 1) {
|
|
matrix[i][j] = val;
|
|
matrix[j][i] = 1 / val;
|
|
} else {
|
|
matrix[j][i] = Math.abs(val);
|
|
matrix[i][j] = 1 / Math.abs(val);
|
|
}
|
|
});
|
|
return matrix;
|
|
}
|
|
|
|
function powerMethod(matrix, maxIter = 1000, threshold = 1e-6) {
|
|
const n = matrix.length;
|
|
let v = Array(n).fill(1 / n);
|
|
for (let iter = 0; iter < maxIter; iter++) {
|
|
const Av = Array(n).fill(0);
|
|
for (let i = 0; i < n; i++)
|
|
for (let j = 0; j < n; j++)
|
|
Av[i] += matrix[i][j] * v[j];
|
|
const sum = Av.reduce((a, b) => a + b, 0);
|
|
const vNew = Av.map(x => x / sum);
|
|
const maxDiff = Math.max(...vNew.map((x, k) => Math.abs(x - v[k])));
|
|
v = vNew;
|
|
if (maxDiff < threshold) break;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
function calcConsistency(matrix, weights) {
|
|
const n = matrix.length;
|
|
const RI = [0, 0, 0.58, 0.90, 1.12, 1.24, 1.32, 1.41, 1.45];
|
|
const Aw = Array(n).fill(0);
|
|
for (let i = 0; i < n; i++)
|
|
for (let j = 0; j < n; j++)
|
|
Aw[i] += matrix[i][j] * weights[j];
|
|
const lambdaMax = Aw.reduce((s, v, i) => s + v / weights[i], 0) / n;
|
|
const CI = (lambdaMax - n) / (n - 1);
|
|
const CR = CI / RI[n];
|
|
return { lambdaMax, CI, CR };
|
|
}
|
|
|
|
function calcModelScore(caseData, weights) {
|
|
return VARS.reduce((sum, varKey, i) => sum + caseData[varKey] * weights[i], 0);
|
|
}
|
|
|
|
// Slider positions -4 to +4 map to AHP odd values 1,3,5,7,9
|
|
// pos=0 → 1 (equal)
|
|
// pos<0 → left more important, AHP value = 2*|pos|+1 (1,3,5,7,9)
|
|
// pos>0 → right more important, stored as negative: -(2*pos+1)
|
|
function sliderToStored(sliderPos) {
|
|
if (sliderPos === 0) return 1;
|
|
if (sliderPos < 0) return 2 * Math.abs(sliderPos) + 1;
|
|
return -(2 * sliderPos + 1);
|
|
}
|
|
|
|
function storedToSlider(val) {
|
|
if (val === 1) return 0;
|
|
if (val > 1) return -((val - 1) / 2); // left was important
|
|
return (Math.abs(val) - 1) / 2; // right was important
|
|
}
|
|
|
|
function ahpValueToScale(absVal) {
|
|
// absVal is 2-9
|
|
const levels = { 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9' };
|
|
return absVal;
|
|
}
|
|
|
|
function runAHP(pairwiseRaw) {
|
|
const matrix = buildMatrix(pairwiseRaw);
|
|
const weights = powerMethod(matrix);
|
|
const { lambdaMax, CI, CR } = calcConsistency(matrix, weights);
|
|
return { weights, lambdaMax, CI, CR, matrix };
|
|
}
|