first commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
// 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user