forked from hangshuo652/aurak
fix: 出题分配算法重构 + 20题模板 + 非技术人员模板
问题修复: - Math.round 导致合计偏差(3题→4题,5题→6题等) - 补充轮次破坏维度权重比例 - 各维度无题型比例控制 修正方案: - floor + remainder 分配法,保证合计永远 = count - 按weight降序分配余数,权重高的优先 - 不足时仅补充轮,但仍保持维度优先 - 补充轮日志记录各维度实际分配数 新增功能: - 技术人员模板 20题 (PROMPT:30/LLM:30/IDE:20/DEV_PATTERN:20) - 非技术人员模板 10题 (PROMPT:50/LLM:30/WORK_CAPABILITY:20) → 面向非技术角色,不考核IDE和开发范式 - 模板支持任意维度组合,可灵活配置 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -534,30 +534,59 @@ export class QuestionBankService {
|
||||
|
||||
const usedIds = new Set<string>();
|
||||
const selected: QuestionBankItem[] = [];
|
||||
let availableItems = [...allItems];
|
||||
const selectedDetail: string[] = [];
|
||||
|
||||
if (dimensionWeights && dimensionWeights.length > 0) {
|
||||
// ── 按权重公平分配题数(floor + remainder,保证总和 = count)──
|
||||
const totalWeight = dimensionWeights.reduce((s, d) => s + d.weight, 0);
|
||||
for (const dw of dimensionWeights) {
|
||||
const dimName = dw.name as QuestionDimension;
|
||||
const targetForDim = Math.round(count * dw.weight / totalWeight);
|
||||
let pool = availableItems.filter(i => i.dimension === dimName && !usedIds.has(i.id));
|
||||
|
||||
// 第一轮: floor 分配
|
||||
const targets: { dw: typeof dimensionWeights[0]; target: number; taken: number }[]
|
||||
= dimensionWeights.map(dw => ({
|
||||
dw,
|
||||
target: Math.floor(count * dw.weight / totalWeight),
|
||||
taken: 0,
|
||||
}));
|
||||
|
||||
let allocated = targets.reduce((s, t) => s + t.target, 0);
|
||||
// 第二轮: 按 weight 降序分配余数(保证总和 = count)
|
||||
const remainder = count - allocated;
|
||||
if (remainder > 0) {
|
||||
const sortedByWeight = [...targets].sort((a, b) => b.dw.weight - a.dw.weight);
|
||||
for (let i = 0; i < remainder; i++) {
|
||||
sortedByWeight[i % sortedByWeight.length].target++;
|
||||
}
|
||||
}
|
||||
|
||||
// 各维度抽题
|
||||
for (const t of targets) {
|
||||
const dimName = t.dw.name as QuestionDimension;
|
||||
let pool = allItems.filter(i => i.dimension === dimName && !usedIds.has(i.id));
|
||||
pool = this.shuffleArray(pool);
|
||||
const take = Math.min(targetForDim, pool.length);
|
||||
const take = Math.min(t.target, pool.length);
|
||||
for (let i = 0; i < take; i++) {
|
||||
selected.push(pool[i]);
|
||||
usedIds.add(pool[i].id);
|
||||
t.taken++;
|
||||
}
|
||||
selectedDetail.push(`${dimName}: ${t.taken}/${t.target}`);
|
||||
}
|
||||
|
||||
// 如果有维度出题不足,从其他维度补
|
||||
if (selected.length < count) {
|
||||
const remaining = allItems.filter(i => !usedIds.has(i.id));
|
||||
const shuffled = this.shuffleArray(remaining);
|
||||
for (const item of shuffled) {
|
||||
if (selected.length >= count) break;
|
||||
selected.push(item);
|
||||
usedIds.add(item.id);
|
||||
selectedDetail.push(`${item.dimension}(补)`);
|
||||
}
|
||||
}
|
||||
availableItems = availableItems.filter(i => !usedIds.has(i.id));
|
||||
availableItems = this.shuffleArray(availableItems);
|
||||
while (selected.length < count && availableItems.length > 0) {
|
||||
const item = availableItems.pop()!;
|
||||
selected.push(item);
|
||||
usedIds.add(item.id);
|
||||
}
|
||||
} else {
|
||||
// ── 无维度权重:轮询 DIMENSIONS 列表 ──
|
||||
let dimIdx = 0;
|
||||
const availableItems = [...allItems];
|
||||
while (selected.length < count && availableItems.length > 0) {
|
||||
const dim = DIMENSIONS[dimIdx % DIMENSIONS.length];
|
||||
dimIdx++;
|
||||
@@ -567,13 +596,13 @@ export class QuestionBankService {
|
||||
const item = pool[idx];
|
||||
selected.push(item);
|
||||
usedIds.add(item.id);
|
||||
availableItems = availableItems.filter(i => i.id !== item.id);
|
||||
availableItems.splice(availableItems.findIndex(i => i.id === item.id), 1);
|
||||
}
|
||||
if (dimIdx >= DIMENSIONS.length * 3) break;
|
||||
}
|
||||
if (selected.length < count && availableItems.length > 0) {
|
||||
availableItems = this.shuffleArray(availableItems);
|
||||
for (const item of availableItems) {
|
||||
const shuffled = this.shuffleArray(availableItems);
|
||||
for (const item of shuffled) {
|
||||
if (selected.length >= count) break;
|
||||
if (!usedIds.has(item.id)) {
|
||||
selected.push(item);
|
||||
@@ -583,6 +612,7 @@ export class QuestionBankService {
|
||||
}
|
||||
}
|
||||
|
||||
// 最后兜底
|
||||
if (selected.length < count) {
|
||||
const remaining = allItems.filter((i) => !usedIds.has(i.id));
|
||||
const shuffled = remaining.sort(() => Math.random() - 0.5);
|
||||
@@ -594,7 +624,7 @@ export class QuestionBankService {
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`[selectQuestions] Selected ${selected.length} questions from bank ${bankId}`,
|
||||
`[selectQuestions] Selected ${selected.length}/${count} questions from bank ${bankId} | ${selectedDetail.join(', ')}`,
|
||||
);
|
||||
return this.shuffleArray(selected);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user