Spaces:
Sleeping
Sleeping
File size: 10,288 Bytes
78c5cf9 67a8b0c 37ea041 67a8b0c 78c5cf9 37ea041 78c5cf9 67a8b0c 37ea041 67a8b0c 37ea041 67a8b0c 37ea041 67a8b0c 37ea041 67a8b0c 37ea041 67a8b0c 37ea041 67a8b0c 37ea041 67a8b0c 37ea041 67a8b0c bdfccdc 67a8b0c 37ea041 bdfccdc 67a8b0c bdfccdc 37ea041 bdfccdc 37ea041 bdfccdc 37ea041 bdfccdc 37ea041 bdfccdc 37ea041 67a8b0c bdfccdc 67a8b0c bdfccdc 37ea041 67a8b0c 37ea041 bdfccdc 37ea041 bdfccdc 37ea041 bdfccdc 37ea041 bdfccdc 37ea041 bdfccdc 37ea041 67a8b0c 37ea041 bdfccdc 37ea041 67a8b0c 37ea041 67a8b0c 37ea041 67a8b0c 37ea041 67a8b0c bdfccdc 67a8b0c bdfccdc 78c5cf9 67a8b0c 78c5cf9 67a8b0c 78c5cf9 67a8b0c 78c5cf9 bdfccdc 67a8b0c 8d16fdf 78c5cf9 bdfccdc 78c5cf9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
const {
User, Student, Score, AttendanceModel, ClassModel,
Course, ConfigModel
} = require('./models');
/**
* 格式化当前日期
*/
const getCurrentDateInfo = () => {
const now = new Date();
const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日 ${days[now.getDay()]}`;
};
/**
* 辅助函数:解析学年前缀
* 例如 "2023-2024学年 第二学期" -> "2023-2024学年"
*/
const getSchoolYearPrefix = (semester) => {
if (!semester) return null;
const match = semester.match(/^(.+?学年)/);
return match ? match[1] : null;
};
/**
* 构建学生画像上下文 (学生视角)
*/
async function buildStudentContext(username, schoolId) {
const student = await Student.findOne({
$or: [{ studentNo: username }, { name: username }],
schoolId
});
if (!student) return "无法找到该学生的详细档案。";
// 获取当前学期配置
const config = await ConfigModel.findOne({ key: 'main' });
const currentSemester = config ? config.semester : null;
// 逻辑:尝试拉取本学年的所有数据 (第一学期 + 第二学期)
const schoolYearPrefix = getSchoolYearPrefix(currentSemester);
const scoreQuery = {
studentNo: student.studentNo,
schoolId
};
if (schoolYearPrefix) {
// 匹配该学年开头的所有学期
scoreQuery.semester = { $regex: new RegExp('^' + schoolYearPrefix) };
} else if (currentSemester) {
scoreQuery.semester = currentSemester;
}
// 获取成绩 (稍微放宽限制以容纳整年数据)
const recentScores = await Score.find(scoreQuery).sort({ semester: -1, _id: -1 }).limit(100);
// 获取考勤概况
const attendanceStats = await AttendanceModel.aggregate([
{ $match: { studentId: student._id.toString() } },
{ $group: { _id: "$status", count: { $sum: 1 } } }
]);
const absentCount = attendanceStats.find(a => a._id === 'Absent')?.count || 0;
const leaveCount = attendanceStats.find(a => a._id === 'Leave')?.count || 0;
let prompt = `
### 当前用户身份:学生 (个人视图)
- **姓名**: ${student.name}
- **班级**: ${student.className}
- **学号**: ${student.studentNo}
- **积分(小红花)**: ${student.flowerBalance} 🌺
- **当前学期**: ${currentSemester || '全部'}
### 个人学习数据
`;
if (recentScores.length > 0) {
// 格式化输出,带上学期标识
const scoreList = recentScores.map(s => {
const semShort = s.semester ? s.semester.replace(schoolYearPrefix || '', '').trim() : '';
return `${s.courseName}: ${s.score} (${semShort} ${s.examName||s.type})`;
}).join('\n');
prompt += `#### 成绩记录 (本学年):\n${scoreList}\n`;
} else {
prompt += `- **近期成绩**: 暂无记录\n`;
}
if (absentCount > 0 || leaveCount > 0) {
prompt += `- **考勤**: 缺勤 ${absentCount} 次,请假 ${leaveCount} 次。\n`;
} else {
prompt += `- **考勤**: 全勤。\n`;
}
return prompt;
}
/**
* 构建教师画像上下文 (严格权限版 + 全量数据)
*/
async function buildTeacherContext(username, schoolId) {
const user = await User.findOne({ username, schoolId });
if (!user) return "无法找到该教师档案。";
const homeroomClass = user.homeroomClass;
// 1. 查找任教课程信息 (确定科任权限)
const courses = await Course.find({
schoolId,
$or: [{ teacherId: user._id }, { teacherName: user.trueName || user.username }]
});
// 2. 确定有权限的班级列表
const authorizedClasses = new Set();
const teachingSubjectsMap = {}; // Map<ClassName, Set<SubjectName>>
// 班主任权限:拥有该班级所有数据权限
if (homeroomClass) {
authorizedClasses.add(homeroomClass);
}
// 科任权限:拥有特定班级的特定科目权限
courses.forEach(c => {
if (c.className) {
authorizedClasses.add(c.className);
if (!teachingSubjectsMap[c.className]) teachingSubjectsMap[c.className] = new Set();
teachingSubjectsMap[c.className].add(c.courseName);
}
});
const classList = Array.from(authorizedClasses);
if (classList.length === 0) {
return `### 当前用户身份:教师 (${user.trueName})\n目前系统显示您未绑定任何班级。请告知用户去“班级管理”或“课程安排”进行绑定。`;
}
// 3. 全量拉取相关班级的学生
const students = await Student.find({
schoolId,
className: { $in: classList },
status: 'Enrolled'
}).sort({ seatNo: 1, studentNo: 1 }); // 按座号排序
if (students.length === 0) {
return `### 当前用户身份:教师\n管理班级: [${classList.join(', ')}]\n但系统未在这些班级找到学生档案。`;
}
const studentNos = students.map(s => s.studentNo);
// 4. 拉取这些学生的成绩 (限制为本学年:包含第一学期和第二学期)
const config = await ConfigModel.findOne({ key: 'main' });
const currentSemester = config ? config.semester : null;
const schoolYearPrefix = getSchoolYearPrefix(currentSemester);
const scoreQuery = {
schoolId,
studentNo: { $in: studentNos },
status: 'Normal'
};
// 关键修复:使用正则匹配整个学年 (例如 "2024-2025学年")
if (schoolYearPrefix) {
scoreQuery.semester = { $regex: new RegExp('^' + schoolYearPrefix) };
} else if (currentSemester) {
// 如果无法解析年份,回退到当前学期
scoreQuery.semester = currentSemester;
}
const allScores = await Score.find(scoreQuery);
// 5. 构建 Prompt
let prompt = `
### 当前用户身份:教师 (${user.trueName || username})
### 权限范围 (严格遵守)
你只能回答下列班级的数据。如果用户询问其他班级(例如用户只教一年级,却问四年级),请礼貌拒绝,说明权限不足。
管理班级: [${classList.join(', ')}]
数据范围: ${schoolYearPrefix ? schoolYearPrefix + " (全学年)" : (currentSemester || '所有历史')}
### 详细班级数据
`;
for (const cls of classList) {
const isClassHomeroom = cls === homeroomClass;
const subjects = teachingSubjectsMap[cls] ? Array.from(teachingSubjectsMap[cls]) : [];
const roleText = isClassHomeroom ? "班主任 (全科权限)" : `任课老师 (科目: ${subjects.join(', ')})`;
prompt += `\n#### 🏫 ${cls} [${roleText}]\n`;
const clsStudents = students.filter(s => s.className === cls);
if (clsStudents.length === 0) {
prompt += "(暂无学生)\n";
continue;
}
prompt += `学生总数: ${clsStudents.length}人\n名单及成绩 (格式: [学期]科目:分数):\n`;
for (const s of clsStudents) {
let sScores = allScores.filter(sc => sc.studentNo === s.studentNo);
// 如果不是班主任,仅展示自己教的科目成绩
if (!isClassHomeroom && subjects.length > 0) {
sScores = sScores.filter(sc => subjects.includes(sc.courseName));
}
// 格式: 张三(01号): [一]语文:90, [二]语文:85...
const scoreStr = sScores.length > 0
? sScores.map(sc => {
// 简化学期显示,例如 "第一学期" -> "一", "第二学期" -> "二"
let semLabel = "";
if (sc.semester && schoolYearPrefix) {
if (sc.semester.includes("第一")) semLabel = "[上]";
else if (sc.semester.includes("第二")) semLabel = "[下]";
}
return `${semLabel}${sc.courseName}:${sc.score}`;
}).join(', ')
: (isClassHomeroom ? "暂无本学年成绩" : "无本科目成绩");
prompt += `- ${s.name} (${s.seatNo ? s.seatNo+'号' : '无座号'}): ${scoreStr}\n`;
}
}
return prompt;
}
/**
* 构建管理员/校长画像上下文
*/
async function buildAdminContext(role, schoolId) {
return `### 当前用户身份:${role === 'PRINCIPAL' ? '校长' : '超级管理员'}\n你拥有最高权限,但请注意保护隐私。`;
}
/**
* 主入口
*/
async function buildUserContext(username, role, schoolId) {
try {
const dateStr = getCurrentDateInfo();
let roleContext = "";
if (role === 'STUDENT') {
roleContext = await buildStudentContext(username, schoolId);
} else if (role === 'TEACHER') {
roleContext = await buildTeacherContext(username, schoolId);
} else if (role === 'ADMIN' || role === 'PRINCIPAL') {
roleContext = await buildAdminContext(role, schoolId);
}
return `
---
【系统注入上下文】
当前时间: ${dateStr}
以下是当前用户权限范围内的 **全量真实数据**。
${roleContext}
【AI 行为准则】
1. **角色设定**: 你是学校的AI智能助手。你的职责是协助查询校内信息,同时也乐于回答通用的百科/日常问题。
2. **数据查询规则**:
- 当用户询问 **学校内部数据** (如: 学生成绩、考勤、班级名单、老师信息) 时,**必须且只能** 使用上述提供的【系统注入上下文】。
- 如果用户询问的 **校内数据** 不在上下文中 (例如: 用户只教一年级,却问三年级的数据),请明确告知“无权限查看该数据”。
3. **通用问答规则**:
- 当用户询问 **非校内数据** (如: 天气、历史、写代码、翻译、闲聊) 时,请忽略权限限制,利用你的通用知识库或联网搜索功能正常回答。不要因为上下文中没有天气数据就拒绝回答。
4. **联网搜索**: 如果用户开启了联网搜索,积极搜索最新信息回答通用问题。
---
`;
} catch (e) {
console.error("Context build failed:", e);
return "";
}
}
module.exports = { buildUserContext };
|