feat: add unified learning workflows and six-tab assistant
This commit is contained in:
+16
-15
@@ -1,7 +1,6 @@
|
||||
/* Exact answer matching and role-scoped local search. */
|
||||
/* Exact answer matching and unified local search. Roles remain source metadata. */
|
||||
(function (root) {
|
||||
'use strict';
|
||||
const ROLES = ['主操', '班长', '副操'];
|
||||
const cache = new WeakMap();
|
||||
|
||||
function normalize(value) {
|
||||
@@ -35,8 +34,7 @@
|
||||
if (saved && saved.revision === snapshot.revision && saved.records === snapshot.records) return saved.index;
|
||||
const index = new Map();
|
||||
snapshot.records.forEach(record => {
|
||||
if (!ROLES.includes(record.role)) return;
|
||||
const key = record.role + '\n' + groupKey(record);
|
||||
const key = groupKey(record);
|
||||
if (!index.has(key)) index.set(key, []);
|
||||
index.get(key).push(record);
|
||||
});
|
||||
@@ -70,15 +68,19 @@
|
||||
|
||||
function match(question, context, snapshot) {
|
||||
context = context || {};
|
||||
if (!ROLES.includes(context.role)) return result('unsupported', '请先选择主操、班长或副操');
|
||||
if (!question || !['单选', '多选', '判断'].includes(normalizeType(question.type))) return result('unsupported', '当前题型暂不支持自动选择');
|
||||
if (!normalize(question.question) || !Array.isArray(question.options) || question.options.length < 2) return result('unsupported', '题干或选项尚未完整加载');
|
||||
if ((question.questionImages && question.questionImages.length) || question.options.some(option => !normalize(option.text) || (option.images && option.images.length))) return result('unsupported', '含图片的题目需要人工核对');
|
||||
const currentTexts = question.options.map(option => normalize(option.text));
|
||||
const currentLabels = question.options.map(option => normalizedLabel(option.label));
|
||||
if (new Set(currentTexts).size !== currentTexts.length || new Set(currentLabels).size !== currentLabels.length || currentLabels.some(label => !/^[A-H]$/.test(label))) return result('unsupported', '当前选项文字或字母重复,无法唯一映射');
|
||||
let records = getIndex(snapshot).get(context.role + '\n' + groupKey(question)) || [];
|
||||
if (!records.length) return result('missing', '所选岗位题库没有题型、题干和全部选项均一致的记录');
|
||||
let records = getIndex(snapshot).get(groupKey(question)) || [];
|
||||
if (!records.length) return result('missing', '统一题库没有题型、题干和全部选项均一致的记录');
|
||||
if (Array.isArray(context.courses)) {
|
||||
const allowed = new Set(context.courses.map(courseNormalize).filter(Boolean));
|
||||
records = records.filter(record => allowed.has(courseNormalize(record.course)));
|
||||
if (!records.length) return result('missing', '已确认的题库主题中没有完全一致的题目记录');
|
||||
}
|
||||
let sourceMatched = false;
|
||||
const identity = sourceIdentity(context.sourceUrl);
|
||||
if (identity) {
|
||||
@@ -99,14 +101,12 @@
|
||||
if (normalizeType(question.type) !== '多选' && selected.length !== 1) return result('unsupported', '单选/判断题的参考答案数量不正确', records);
|
||||
// Keep label/text pairs in current-page order, but obtain each text from bank evidence.
|
||||
const evidenceTexts = selected.map(option => wanted.find(text => normalize(text) === normalize(option.text)));
|
||||
return result('matched', sourceMatched ? '岗位、课程、题干及选项完全匹配' : '岗位、题干及选项完全匹配,所有来源答案一致', records, selected.map(option => normalizedLabel(option.label)), evidenceTexts);
|
||||
return result('matched', sourceMatched ? '课程来源、题干及选项完全匹配' : '题干及选项完全匹配,所有来源答案一致', records, selected.map(option => normalizedLabel(option.label)), evidenceTexts);
|
||||
}
|
||||
|
||||
function search(query, settings, snapshot) {
|
||||
settings = settings || {};
|
||||
const role = settings.role;
|
||||
const empty = reason => ({ items: [], total: 0, reason, role: role || '', query: String(query || ''), hasMore: false });
|
||||
if (!ROLES.includes(role)) return empty('请先选择主操、班长或副操');
|
||||
const empty = reason => ({ items: [], total: 0, reason, role: '', query: String(query || ''), hasMore: false });
|
||||
const fullQuery = searchNormalize(query);
|
||||
if (!fullQuery) return empty('请输入题干、关键词或选项文字');
|
||||
const tokens = fullQuery.split(/\s+/).filter(Boolean);
|
||||
@@ -114,13 +114,14 @@
|
||||
const records = snapshot && Array.isArray(snapshot.records) ? snapshot.records : [];
|
||||
const groups = new Map(), conflicts = new Map();
|
||||
records.forEach(record => {
|
||||
if (record.role !== role || (course && courseNormalize(record.course) !== course)) return;
|
||||
if (course && courseNormalize(record.course) !== course) return;
|
||||
if (settings.sourceUrl && sourceIdentity(record.sourceUrl) !== sourceIdentity(settings.sourceUrl)) return;
|
||||
const baseKey = groupKey(record), answerKey = correctKey(record), key = baseKey + '\n' + answerKey;
|
||||
if (!conflicts.has(baseKey)) conflicts.set(baseKey, new Set());
|
||||
conflicts.get(baseKey).add(answerKey);
|
||||
if (!groups.has(key)) groups.set(key, { ...record, sources: [], conflict: false, sourceCount: 0, _baseKey: baseKey, _sources: new Set() });
|
||||
const group = groups.get(key);
|
||||
const source = { role: record.role, course: record.course, sourceUrl: record.sourceUrl || '', number: record.number };
|
||||
const source = { role: record.role || '', course: record.course, sourceUrl: record.sourceUrl || '', sourceFile: record.sourceFile || '', number: record.number };
|
||||
const sourceKey = JSON.stringify(source);
|
||||
if (!group._sources.has(sourceKey)) { group._sources.add(sourceKey); group.sources.push(source); group.sourceCount++; }
|
||||
});
|
||||
@@ -141,10 +142,10 @@
|
||||
const total = items.length;
|
||||
const limit = Math.max(1, Math.min(100, Number(settings.limit) || 20));
|
||||
const offset = Math.max(0, Math.floor(Number(settings.offset) || 0));
|
||||
return { items: items.slice(offset, offset + limit), total, role, query: String(query), reason: total ? '' : '当前岗位未找到符合条件的题目', hasMore: offset + limit < total };
|
||||
return { items: items.slice(offset, offset + limit), total, role: '', query: String(query), reason: total ? '' : '统一题库未找到符合条件的题目', hasMore: offset + limit < total };
|
||||
}
|
||||
|
||||
const api = Object.freeze({ version: '2.0.0', normalize, searchNormalize, normalizeType, optionKey, questionKey, sourceIdentity, match, search });
|
||||
const api = Object.freeze({ version: '3.0.0', normalize, searchNormalize, normalizeType, optionKey, questionKey, sourceIdentity, match, search });
|
||||
root.YianAnswerMatcher = api;
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
||||
})(typeof window !== 'undefined' ? window : globalThis);
|
||||
|
||||
Reference in New Issue
Block a user