数据查询优化
This commit is contained in:
+75
-21
@@ -357,12 +357,46 @@ function gradeForRank(rank, cohortSize) {
|
||||
return 'D';
|
||||
}
|
||||
|
||||
const resultIndexCache = new WeakMap();
|
||||
const examSummaryCache = new WeakMap();
|
||||
|
||||
function resultIndexes(db) {
|
||||
let indexes = resultIndexCache.get(db);
|
||||
if (indexes) return indexes;
|
||||
const publishedScoresBySubject = new Map();
|
||||
const publishedByRegistration = new Map();
|
||||
for (const result of db.results) {
|
||||
if (!result.published) continue;
|
||||
if (!publishedScoresBySubject.has(result.subjectId)) publishedScoresBySubject.set(result.subjectId, []);
|
||||
publishedScoresBySubject.get(result.subjectId).push(Number(result.score));
|
||||
if (!publishedByRegistration.has(result.registrationId)) publishedByRegistration.set(result.registrationId, []);
|
||||
publishedByRegistration.get(result.registrationId).push(result);
|
||||
}
|
||||
for (const scores of publishedScoresBySubject.values()) scores.sort((left, right) => left - right);
|
||||
indexes = { publishedScoresBySubject, publishedByRegistration };
|
||||
resultIndexCache.set(db, indexes);
|
||||
return indexes;
|
||||
}
|
||||
|
||||
function countScoresGreaterThan(sortedScores, score) {
|
||||
let low = 0;
|
||||
let high = sortedScores.length;
|
||||
while (low < high) {
|
||||
const middle = Math.floor((low + high) / 2);
|
||||
if (sortedScores[middle] <= score) low = middle + 1;
|
||||
else high = middle;
|
||||
}
|
||||
return sortedScores.length - low;
|
||||
}
|
||||
|
||||
function resultRankInfo(db, result, scoreOverride = result?.score) {
|
||||
if (!result?.subjectId || !Number.isFinite(Number(scoreOverride))) return { rank: null, cohortSize: 0, rankPercent: null, grade: '' };
|
||||
const score = Number(scoreOverride);
|
||||
const peers = db.results.filter(item => item.id !== result.id && item.subjectId === result.subjectId && item.published);
|
||||
const cohortSize = peers.length + 1;
|
||||
const rank = 1 + peers.filter(item => Number(item.score) > score).length;
|
||||
const scores = resultIndexes(db).publishedScoresBySubject.get(result.subjectId) || [];
|
||||
const cohortSize = scores.length + (result.published ? 0 : 1);
|
||||
let greater = countScoresGreaterThan(scores, score);
|
||||
if (result.published && Number(result.score) > score) greater -= 1;
|
||||
const rank = 1 + Math.max(0, greater);
|
||||
const rankPercent = Number((rank / cohortSize * 100).toFixed(2));
|
||||
return { rank, cohortSize, rankPercent, grade: gradeForRank(rank, cohortSize) };
|
||||
}
|
||||
@@ -379,15 +413,43 @@ function subjectPassEvaluation(db, result, subject, scoreOverride = result?.scor
|
||||
return { ...resultRankInfo(db, result, scoreOverride), qualified: Number(scoreOverride) >= passScore, passScore, cutoffRank: null };
|
||||
}
|
||||
|
||||
function examSummaryIndexes(db) {
|
||||
let indexes = examSummaryCache.get(db);
|
||||
if (indexes) return indexes;
|
||||
const examById = new Map(db.exams.map(exam => [exam.id, exam]));
|
||||
const publishedByRegistration = resultIndexes(db).publishedByRegistration;
|
||||
const baseByRegistration = new Map();
|
||||
const totalsByCohort = new Map();
|
||||
for (const registration of db.registrations) {
|
||||
if (registration.status !== 'approved') continue;
|
||||
const exam = examById.get(registration.examId);
|
||||
if (!exam) continue;
|
||||
const subjects = exam.subjects.filter(subject => registration.subjectIds.includes(subject.id));
|
||||
const published = publishedByRegistration.get(registration.id) || [];
|
||||
const resultsBySubject = new Map(published.map(result => [result.subjectId, result]));
|
||||
const complete = subjects.length > 0 && subjects.every(subject => resultsBySubject.has(subject.id));
|
||||
const total = subjects.reduce((sum, subject) => sum + Number(resultsBySubject.get(subject.id)?.score || 0), 0);
|
||||
const fullScore = subjects.reduce((sum, subject) => sum + Number(subject.fullScore || 0), 0);
|
||||
const subjectKey = [...registration.subjectIds].sort().join('|');
|
||||
const cohortKey = `${exam.id}\u0000${subjectKey}`;
|
||||
const base = { exam, subjects, published, resultsBySubject, complete, total, fullScore, cohortKey };
|
||||
baseByRegistration.set(registration.id, base);
|
||||
if (complete) {
|
||||
if (!totalsByCohort.has(cohortKey)) totalsByCohort.set(cohortKey, []);
|
||||
totalsByCohort.get(cohortKey).push(total);
|
||||
}
|
||||
}
|
||||
for (const totals of totalsByCohort.values()) totals.sort((left, right) => left - right);
|
||||
indexes = { baseByRegistration, totalsByCohort };
|
||||
examSummaryCache.set(db, indexes);
|
||||
return indexes;
|
||||
}
|
||||
|
||||
function examResultSummary(db, registration) {
|
||||
const exam = db.exams.find(item => item.id === registration.examId);
|
||||
if (!exam) return null;
|
||||
const subjects = exam.subjects.filter(subject => registration.subjectIds.includes(subject.id));
|
||||
const published = db.results.filter(result => result.registrationId === registration.id && result.published);
|
||||
const resultsBySubject = new Map(published.map(result => [result.subjectId, result]));
|
||||
const complete = subjects.length > 0 && subjects.every(subject => resultsBySubject.has(subject.id));
|
||||
const total = subjects.reduce((sum, subject) => sum + Number(resultsBySubject.get(subject.id)?.score || 0), 0);
|
||||
const fullScore = subjects.reduce((sum, subject) => sum + Number(subject.fullScore || 0), 0);
|
||||
const summaryIndexes = examSummaryIndexes(db);
|
||||
const base = summaryIndexes.baseByRegistration.get(registration.id);
|
||||
if (!base) return null;
|
||||
const { exam, subjects, published, resultsBySubject, complete, total, fullScore, cohortKey } = base;
|
||||
const scoreRatio = fullScore ? total / fullScore * 100 : 0;
|
||||
const policy = exam.passPolicy === 'score_ratio' ? 'rank_percent' : (exam.passPolicy || 'rank_percent');
|
||||
const value = Number(exam.passValue ?? 60);
|
||||
@@ -402,17 +464,9 @@ function examResultSummary(db, registration) {
|
||||
});
|
||||
if (complete && policy === 'none') qualified = null;
|
||||
if (complete && policy === 'rank_percent') {
|
||||
const subjectKey = [...registration.subjectIds].sort().join('|');
|
||||
const totals = db.registrations
|
||||
.filter(item => item.examId === exam.id && item.status === 'approved' && [...item.subjectIds].sort().join('|') === subjectKey)
|
||||
.map(item => {
|
||||
const itemResults = db.results.filter(result => result.registrationId === item.id && result.published);
|
||||
if (!item.subjectIds.every(id => itemResults.some(result => result.subjectId === id))) return null;
|
||||
return itemResults.filter(result => item.subjectIds.includes(result.subjectId)).reduce((sum, result) => sum + Number(result.score), 0);
|
||||
})
|
||||
.filter(item => item != null);
|
||||
const totals = summaryIndexes.totalsByCohort.get(cohortKey) || [];
|
||||
cohortSize = totals.length;
|
||||
rank = 1 + totals.filter(item => item > total).length;
|
||||
rank = 1 + countScoresGreaterThan(totals, total);
|
||||
qualified = rank <= Math.max(1, Math.ceil(cohortSize * value / 100));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user