关于 education-cloud-academic-calendar-generate
此 Agent skill 根据日历文档或自然语言信息,为 Education Cloud 组织生成学年、学期和学期分段记录。它会校验日期、检查重复项,并在创建记录前要求确认。
主要功能
- 01解析文档或自然语言中的日历日期
- 02生成学年、学期和分段记录
- 03校验日期范围与层级关系
- 04检查重复学期并请求确认
适用场景
- 01为学期制或季度制整理学年日历
- 02从日历文档提取学期与注册日期
- 03创建并关联学年、学期和分段记录
MCP 相关
该 skill 通过 Salesforce REST API 操作 Education Cloud 记录,并要求先检查可用的 API 传输方式。它不要求或使用 MCP 服务的特定功能。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill education-cloud-academic-calendar-generate安装后在 Claude Code 中输入/education-cloud-academic-calendar-generate确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/quarter-calendar.jsonsample-output.mdsemester-calendar.jsonreferences/calendar-systems.mddate-validation.mdfoundation_prerequisites.mdscripts/validate_calendar_dates.py详细介绍
Education Cloud 学术日历生成
为 Education Cloud 组织构建基础学术日历基础设施,从文档或自然语言中解析日历数据,并生成经过验证的 Academic Year、Academic Term 和 Academic Session 记录,确保日期范围和机构关联正确。
范围
- 范围内:Academic Year 记录、Academic Term 记录(学期制/季度制/三学期制)、Academic Session 记录(学期细分)、注册窗口、加退课截止日期、日期验证
- 范围外:课程排课(交由课程开设技能处理)、单个课程班级的日期、助学金截止日期、考试安排、机构层级设置(目前没有专门处理此事的技能 — 请手动设置 Accounts/校区)
执行模型
所有 Salesforce 操作都使用相对路径 REST 调用。不要使用 CLI 或 shell 命令访问 Salesforce(sf、sfdx、curl);执行环境可能不提供这些命令。这不适用于技能自带的本地验证脚本(scripts/validate_calendar_dates.py,见第 4 步)——该脚本在本地运行,且不会访问组织。
- 记录 CRUD:sObject REST —
POST /services/data/<ver>/sobjects/<Object>、GET /services/data/<ver>/sobjects/<Object>/<Id>、PATCH /services/data/<ver>/sobjects/<Object>/<Id> - 架构检查:sObject describe —
GET /services/data/<ver>/sobjects/<Object>/describe(写入前确认对象和字段 API 名称存在)。 - SOQL
/query可路由:GET /services/data/<ver>/query,将 SOQL 作为结构化的queryParams: {"q": "..."}传入,不要将其内联到 URL 中作为?q=...— 实测已确认:通过 headless-360 分发时,内联?q=会返回 404(ROUTE_NOT_FOUND/NOT_FOUND),而使用queryParams可稳定返回 200。可用它扫描现有学期/学期时段,或使用创建时捕获的运行时 Id 查找记录,并通过直接调用GET .../sobjects/<Object>/<Id>读取记录 — 两种方式均有效。 - 所有 Id 都是运行时变量,从创建响应的
body.id中捕获 — 绝不要硬编码 Salesforce Id 字面值。 - 动态解析 API 版本 — 参见下文“执行传输”。不要硬编码版本。
必需输入
继续操作前,收集或推断以下信息:
- 日历体系:Semester(秋季/春季/夏季)、Quarter(秋季/冬季/春季/夏季)或 Trimester
- 学年跨度:整个学年的开始和结束日期
- 学期日期:每个学期的上课开始/结束日期、注册开放日期、加退课截止日期
- 学期时段细分:学期内的任何子时段(例如,夏季第 1 学期时段和第 2 学期时段)
如果用户提供日历文档(PDF、CSV、电子表格),请解析并提取所有日期。如果用户提供自然语言描述,请先询问具体日期再继续。
注意:AcademicYear、AcademicTerm 和 AcademicSession 均没有 Account 引用字段 — 日历记录不会关联到 Business Account。关联关系仅限 Year → Term(AcademicYearId)和 Term → Session(AcademicTermId)。
除非另有说明,否则使用以下默认值:
- 日历体系:Semester(秋季、春季、夏季)
- 学年命名:“Academic Year YYYY-YYYY” 格式
- 注册窗口:在学期开始前 3-4 个月开放
- 加退课截止日期:学期开始后 2 周
执行传输
- 解析 API 版本:通过 headless-360 调度调用
GET /services/data/(未指定版本)。响应是一个端点路径映射,这些路径已解析为组织当前支持的最高版本(例如"limits": "/services/data/v69.0/limits"),而不是包含version字段的数组。从任意返回路径中提取版本(正则表达式v\d+\.\d+),并将其用作下文所有调用的<ver>。 - 探测传输健康状态:通过 headless-360 调度调用
GET /services/data/<ver>/limits。返回 2xx → 所有调用都通过它路由。若缺失/返回 4xx/5xx → 使用相同的读取操作探测其他可用传输方式(其他 Salesforce MCP、已认证的sfCLI、自定义 MCP),并使用第一个健康的传输方式,同时告知用户。没有健康的传输方式 → 停止,请用户连接一个;绝不捏造。
工作流
所有步骤都必须按顺序执行。若遇到阻碍,请停止并询问缺失的上下文。
-
验证基础前提——遵循
references/foundation_prerequisites.md:组织版本、Lightning Experience、Education Cloud 许可证、当前运行用户的 EDU 访问权限、Education Cloud Foundation 已启用(若未启用,先确认再启用以自行修复)。在所有检查通过之前,不要继续执行步骤 1。 -
确认校历制度
- 询问用户:采用学期制、季度制还是三学期制?
- 根据制度类型确定预期的学期名称
-
收集校历数据
- 如果用户提供文档(PDF、CSV、电子表格):解析并提取年份、学期和课程时段日期
- 如果用户用自然语言提供信息:从描述中提取日期
- 如果信息不足:询问具体日期(学期开始/结束日期、注册窗口、截止日期)
-
验证对象和字段架构
- 写入之前,确认组织中存在你将设置的每个对象和字段:
GET /services/data/<ver>/sobjects/AcademicYear/describe、.../AcademicTerm/describe、.../AcademicSession/describe - 读取
fields数组;将计划使用的字段 API 名称(Name、AcademicYearId、StartDate、EndDate、RegistrationOpenDate、RegistrationCloseDate、Season、AcademicTermId、ClassStartDate、ClassEndDate、AddDropDeadline)与其进行匹配。 - 注意,选项列表字段
Season(AcademicTerm.Season,取值为Fall/Spring/Summer/Winter):只发送该字段picklistValues中存在的值。AcademicSession.Type是另一个选项列表字段(Semester/Trimester/Quarter等),此工作流不会用到——不要将它与Season混淆。 - 如果字段不存在:立即停止——不要使用未经验证的字段名称执行 POST;向用户报告不匹配情况。
- 写入之前,确认组织中存在你将设置的每个对象和字段:
-
验证日期范围
- 将收集到的年份/学期/课程时段数据整理为
scripts/validate_calendar_dates.py顶部所述的 JSON 格式,然后运行一次:python3 scripts/validate_calendar_dates.py <<'EOF' {"academicYear": {...}, "terms": [...], "sessions": [...]} EOF - 该脚本会检查学期结束日期是否晚于开始日期、学年是否涵盖所有学期、注册开放日期是否早于学期开始日期、选课增退截止日期是否在学期范围内、课程时段是否包含在其所属学期内,并计算每个学期的
RegistrationCloseDate。 - 如果
valid为false:停止,向用户报告errors中的每条内容,并要求提供更正后的日期。不要继续执行步骤 5。 warnings(例如有意设置的课程时段重叠)不会阻止流程——在步骤 6 中将每条警告告知用户以便确认。- 保留
computedRegistrationCloseDates供后续使用——步骤 8 将使用它,而不是重新计算。
- 将收集到的年份/学期/课程时段数据整理为
-
检测重复学期
- AcademicTerm 的
Name未设置唯一约束,因此插入重复名称不会报错。创建前,扫描列表以查找名称匹配的现有学期:使用GET /services/data/<ver>/query,并设置queryParams: {"q": "SELECT Id, Name, StartDate, EndDate FROM AcademicTerm WHERE Name = 'Fall 2025'"}— 通过queryParams传递 SOQL,不要将其内联到 URL 的?q=中(内联会返回 404)— 或者,如果用户在本次会话中之前已为今年创建过日历,则重用当时记录的 Id,而不是重新创建。 - 如果发现重复名称,请比较日期,并询问用户是更新现有记录还是跳过。
- 如果重复项检查读取本身失败(非 2xx、传输错误):停止 — 不要继续创建学期。向用户报告失败,并询问如何继续;不要在未告知的情况下创建可能重复的记录。
- AcademicTerm 的
-
展示拟创建的日历结构并确认
- 展示将要创建内容的摘要:学年名称和日期,然后列出每个学期的名称/日期/注册时间窗口/计算出的
RegistrationCloseDate,再按所属学期分组列出每个课期的名称和日期。已填好的示例见examples/sample-output.md。 - 对于步骤 4 中的每条课期重叠警告:说明重叠时段,并询问用户是否确认这是有意安排的。
- 询问用户:“我应该创建所有这些记录吗?”
- 等待用户确认后,再继续步骤 7
- 展示将要创建内容的摘要:学年名称和日期,然后列出每个学期的名称/日期/注册时间窗口/计算出的
-
创建 Academic Year 记录
- 用户在步骤 6 确认后,插入 Academic Year:
POST /services/data/<ver>/sobjects/AcademicYear {"Name": "Academic Year 2025-2026"} - 从响应的
body.id中获取academicYearId,用于关联学期 - 报告:“已创建 Academic Year:Academic Year 2025-2026”
- 用户在步骤 6 确认后,插入 Academic Year:
-
创建 Academic Term 记录
- 对每个学期(Fall、Spring、Summer 或对应的 Quarter),插入记录,并将
AcademicYearId设置为步骤 7 中的academicYearId:POST /services/data/<ver>/sobjects/AcademicTerm {"Name": "Fall 2025", "Season": "Fall", "StartDate": "2025-08-18", "EndDate": "2025-12-12", "AcademicYearId": "<academicYearId>", "RegistrationOpenDate": "2025-04-01", "RegistrationCloseDate": "2025-08-17"}RegistrationCloseDate取自步骤 4 的computedRegistrationCloseDates[<term name>]— 不要手动重新计算。Season是受限选项列表:Fall、Spring、Summer、Winter。AcademicTerm 没有 add/drop 字段 — add/drop 截止日期仅属于 AcademicSession。
- 从响应的
body.id中获取每个termId,用于关联课期 - 报告进度:“已创建 3 个学期:Fall 2025、Spring 2026、Summer 2026”
- 对每个学期(Fall、Spring、Summer 或对应的 Quarter),插入记录,并将
-
创建 Academic Session 记录
- 对每个课期细分(例如 Summer Session 1、Summer Session 2),插入记录,并将
AcademicTermId设置为步骤 8 中所属学期的termId:POST /services/data/<ver>/sobjects/AcademicSession {"Name": "Summer Session 1", "ClassStartDate": "2026-05-26", "ClassEndDate": "2026-07-03", "AddDropDeadline": "2026-06-11", "AcademicTermId": "<termId>"} - 报告进度:“已创建 2 个课期:Summer Session 1、Summer Session 2”
- 对每个课期细分(例如 Summer Session 1、Summer Session 2),插入记录,并将
-
生成完成摘要
- 展示最终摘要:已创建的 Academic Year 及其日期、每个已创建 Term 的日期范围,以及按所属 Term 分组的每个已创建 Session,并附上一行说明 Year → Term → Session 的关联关系。已填好的示例见
examples/sample-output.md。 - 如果存在课期重叠(根据步骤 4 的警告),注明重叠时段以及已确认这是有意安排的。
- 如果有学期因重复而跳过,列出这些学期及原因
- 展示最终摘要:已创建的 Academic Year 及其日期、每个已创建 Term 的日期范围,以及按所属 Term 分组的每个已创建 Session,并附上一行说明 Year → Term → Session 的关联关系。已填好的示例见
按需验证
第 10 步之后随时可用,不是必需的工作流步骤——仅在用户要求抽查记录或查看日历范围时使用。
- 特定记录(例如,“显示我 2025 年秋季学期的信息”):通过已捕获的 Id 冷读记录 —
GET /services/data/<ver>/sobjects/AcademicTerm/<termId>(返回所有字段;不要使用?fields=选择器)— 或者,如果未捕获 Id,则通过名称查找:GET /services/data/<ver>/query,并使用queryParams: {"q": "SELECT Id FROM AcademicTerm WHERE Name = 'Fall 2025'"}(通过queryParams传入 SOQL,不要将其内联为 URL 中的?q=— 内联会导致 404)。以表格形式显示所有字段值,并包含父级层次结构。 - 日历视图(例如,“显示 2026 年夏季学期的所有日期”):通过已捕获的 Id 冷读学期及其每个子课期 —
GET /services/data/<ver>/sobjects/AcademicTerm/<termId>和GET /services/data/<ver>/sobjects/AcademicSession/<sessionId>— 并以表格显示(记录类型、名称、开始日期、结束日期、时长、注册开放时间、注册截止时间、选课增退截止时间)。如适用,计算并显示重叠时段。请参阅examples/sample-output.md查看已填写的示例。
规则 / 约束
| 约束 | 原因 |
|---|---|
| 写入前通过 describe 验证对象和字段 API 名称 | 避免因字段未验证而导致 POST 失败;Season 选项列表值必须与 AcademicTerm.Season 的 picklistValues 匹配 |
| 学期结束日期必须晚于开始日期 | 平台验证会拒绝无效的日期范围 |
| 注册窗口必须在学期开始前开放 | 学生不能在课程开始后注册 |
| 选课增退截止日期(位于 AcademicSession 上)必须晚于或等于学期开始日期 | 课程开始前的截止日期在逻辑上无效;AcademicTerm 没有选课增退字段 |
| 学年必须涵盖其所有子学期 | 学期日期超出学年范围会导致报告问题 |
| 同一学期内的课期允许重叠 | 夏季课期的常见安排(例如,课期 1:5 月至 7 月,课期 2:6 月至 8 月) |
| 学期名称应符合日历系统惯例 | 学期制使用秋季 / 春季 / 夏季,季度制使用秋季 / 冬季 / 春季 / 夏季 |
| 动态解析 API 版本,不要硬编码 | 组织的最高 API 版本会随时间变化;过时的硬编码版本会与组织当前版本脱节 |
创建前通过 SOQL /query 检查是否存在重复学期,并通过 queryParams 传入 q,不要将其内联在 URL 中 | /query 可通过 headless-360 路由,但仅支持使用 queryParams — 内联 ?q= 会导致 404(已通过实时测试确认);学期 Name 没有唯一性约束,因此插入时不会因重复而报错;重复检查读取失败时必须阻止创建,不得跳过 |
注意事项
| 问题 | 解决方法 |
|---|---|
| 用户提供相对日期(“下一个秋季学期”) | 插入前转换为绝对日期(例如,“下一个秋季学期” → “2026 年秋季学期”,并填写具体日期) |
| 重叠课期被标记为错误 | 向用户确认 — 许多院校会有意安排课期重叠(例如,夏季课期 1 和 2 重叠 1 周) |
| 存在日期不同的重复学期 | 询问用户是更新现有记录还是跳过 — 未经确认不得自动更新 |
| 注册窗口在学期开始之后 | 这是无效的 — 请用户提供正确的注册开放日期 |
| 学年结束日期早于最后一个学期的结束日期 | 延长学年以涵盖所有学期,或调整学期日期 |
| 输入包含学期级别的选课增退日期,但没有课期细分 | AcademicTerm 没有选课增退字段 — 向用户说明学期级别与课期级别的字段归属,或建议为每个学期创建一个课期以记录该日期;绝不能将其写入 AcademicTerm,也绝不能悄悄忽略 |
| 用户提供三段式年份范围(2025-2026-2027) | 向用户澄清 — 学年恰好跨度为 2 年(例如,2025-2026) |
| 课期没有父级学期 | 每个课期都必须关联一个学期 — 如果用户提供课期日期但未指定学期,请询问该课期属于哪个学期 |
输出预期
交付内容:
- Academic Year 记录:覆盖所有学期的单条年份记录(例如:“Academic Year 2025-2026”)
- Academic Term 记录:每年 3-4 条学期记录(学期制为秋季、春季、夏季;季度制为秋季、冬季、春季、夏季)
- Academic Session 记录:0-N 条课程阶段记录(学期制系统通常有 2 条夏季课程阶段记录)
- 汇总报告:列出所有已创建记录的表格,包含名称、日期和关联关系
学期通过 AcademicYearId 关联到上级 Academic Year;课程阶段通过 AcademicTermId 关联到上级学期。这些对象没有 Account 引用字段。
跨技能集成
| 需求 | 委派给 |
|---|---|
| 机构层级(Accounts、校区) | 暂无专用技能 — 手动设置 |
| 课程排期和开课信息 | 课程开课信息生成技能 |
参考文件索引
| 文件 | 阅读时机 |
|---|---|
references/foundation_prerequisites.md | 步骤 0 — 组织版本、Lightning Experience、EDU 许可证、权限集分配、启用 Education Cloud Foundation;每次单独调用时,都应在步骤 1 之前运行 |
references/calendar-systems.md | 用户的日历系统不明确或不标准时 |
references/date-validation.md | 验证复杂日期重叠或多课程阶段学期时 |
examples/semester-calendar.json | 验证学期制系统的输出结构 |
examples/quarter-calendar.json | 验证季度制系统的输出结构 |
examples/sample-output.md | 查看拟议结构展示、完成摘要和日历视图表格的完整示例 |
scripts/validate_calendar_dates.py | 在步骤 4 中调用,以验证日期范围并确定性地计算 RegistrationCloseDate |