mcpField
E
education-cloud-course-catalog-migrate

education-cloud-course-catalog-migrate

github.com/forcedotcom数据与数据库1.1k112 次安装1 天前更新
代码仓库

关于 education-cloud-course-catalog-migrate

此 Agent skill 将 CSV、PDF 或网站中的课程目录导入 Education Cloud,创建 Learning 和 LearningCourse 记录。它支持批量处理,并在导入前检查数据完整性、课程关联及现有记录冲突。

主要功能

  1. 01从 CSV、PDF 和网站提取课程数据
  2. 02校验必填字段并识别数据缺口
  3. 03识别讲座与实验课关联
  4. 04分批创建 Learning 与 LearningCourse
  5. 05检测冲突并提供处理选项

适用场景

  1. 01将院系课程目录导入 Education Cloud
  2. 02整理多个来源中的课程字段
  3. 03导入前检查已有课程及重复项

MCP 相关

该 skill 使用 Salesforce sObject REST API 和 Composite API 创建课程记录,并要求检查 API 传输可用性。它提及可探测其他 Salesforce MCP 或自定义 MCP 作为传输方式。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/forcedotcom/sf-skills --skill education-cloud-course-catalog-migrate

安装后在 Claude Code 中输入/education-cloud-course-catalog-migrate确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
gotchas-detail.md
gotchas.md
large-catalog-handling.md
scripts/
batch_courses.py
detect_linked_courses.py
detect_modality_variants.py
resolve_api_version.py
split_course_code.py
validate_completeness.py

详细介绍

将课程目录迁移到 Education Cloud

从多个来源(CSV、PDF、网站)导入课程目录数据,并通过结构检测和数据质量验证批量创建 Learning Course 记录。

范围

  • 范围内:多源数据采集(CSV、PDF、网页);通过 sObject REST API 批量创建 Learning 和 LearningCourse(Composite 批次最多 200 条);检测关联课程(通过 IsLinkedOnly 识别 lecture+lab);解析学分时长(Duration/DurationUnit);映射课程类型;数据质量验证;冲突解决;识别并补齐缺失项
  • 范围外:手动创建单个 Learning Course 记录(交由元数据生成技能处理);创建 Learning Program(Learning.Type = 'LearningProgram' + 单独的子对象);创建 External Learning(Learning.Type = 'ExternalLearning');课程班次/开课安排(CourseOffering 对象);选课/注册工作流(目前没有专用技能——请询问用户);SIS 同步(单独的集成工作流)

必需输入

继续之前,收集或推断以下信息:

  • 目标 org:已启用 Education Cloud 的 Salesforce 活跃 org
  • 主要数据源:包含课程目录数据的 CSV 文件、PDF 文档或网站 URL
  • Business Account:用于关联课程的 Academic Operations Business Account(例如,College of Engineering)
  • 学期(可选):仅作上下文参考,不存储在 Learning Course 中

其他来源(工作流期间收集):

  • 用于补充缺失字段的数据源(描述、类型、映射)
  • 讲座与实验课映射文件(检测到多组成分课程时)
  • 冲突解决决策(发现现有课程时)

未另行指定时采用以下默认值:

  • 课程状态:Active(Learning 上的 IsActive = true)
  • 批次大小:作为 Agent 时,每次 Composite API 调用处理 25 条记录(API 支持 200 条;实际限制是工具调用负载大小——参见步骤 7)
  • 数据质量验证:启用
  • Learning.Type:'LearningCourse'(课程目录条目必需)

执行传输方式

首先解析 API 版本——不要硬编码。 GET /services/data/(不含版本段)会返回已解析为该 org 最高版本的端点映射,而不是 {version, label, url} 列表。将其传入 scripts/resolve_api_version.py,以获取 {"version": "69.0"};下文所有位置(包括传输探测)均使用此版本作为 vXX.X。

然后探测传输健康状态:通过 headless-360 dispatch 调用 GET /services/data/vXX.X/limits。返回 2xx → 所有调用均通过它路由。若不可用或返回 4xx/5xx → 探测其他传输方式(其他 Salesforce MCP、自定义 MCP),使用首个健康的传输方式并告知用户。若没有可用方式 → 停止并请用户连接一种;绝不可捏造结果。(已通过实时测试确认 headless-360 可路由 SOQL /query;/tooling/query 和 ?fields= 尚未确认,应使用 describe + 直接读取记录。)

绝不要退回使用 sf CLI,也不要通过 shell 获取 org 凭据——批次速度慢或过大属于批次大小问题(缩小批次,参见步骤 7),不是传输故障;在某些界面中,通过 shell 调用 CLI 会触发无法绕过的审批门槛。某个版本若对 org 来说过新,即使传输方式健康,也会返回 404 NOT_FOUND——重新读取 GET /services/data/,降至该 org 的最高版本后重试,不要切换传输方式。两种情况详见 references/gotchas.md。


工作流

所有步骤均须按顺序执行。不得跳过或调整顺序。若遇到阻碍,请停止并询问缺失的上下文。

阶段 1 — 导入主要来源
  1. 识别数据来源类型 — 确定主要来源是 CSV、PDF 还是网站 URL。如果用户尚未指定,请让用户提供来源。

  2. 解析主要来源 — 提取课程数据:

    • 对于 CSV:使用 Read 工具加载文件并解析列
    • 对于 PDF:使用 Read 工具(支持 PDF 提取)解析文本。超过 20 页时,按顺序分段读取页面范围,并在验证完整性前合并解析出的行 — 参见 references/large-catalog-handling.md §1
    • 对于网站:使用 WebFetch 工具获取 HTML 并解析课程列表

    查找:课程代码(标识 — 拆分为 SubjectAbbreviation + CourseNumber,参见步骤 8/9)、课程名称(Name)、学分课时(Duration + DurationUnit)、描述(Description)、课程类型(Lecture/Lab/Exam/Drill/Recitation)、先修课程、共同修读课程、关联课程标记(IsLinkedOnly)、授课形式(如果同一课程代码重复出现且 Duration/DurationUnit 不同 — 参见 references/large-catalog-handling.md §2)。

  3. 验证数据完整性 — 将解析出的记录传入完整性检查辅助工具:

    python3 scripts/validate_completeness.py --required "CourseCode,Name,Duration" --recommended "Description,CourseType" <<'JSON'
    [ { "CourseCode": "...", "Name": "...", ... }, ... ]
    JSON
    

    报告会给出每个字段的已提供/缺失计数。不会检查可选字段(先修课程、共同修读课程、关联课程)——缺少这些字段不会阻碍进度。

    如果任何必填字段显示 missing > 0,请报告错误并停止。如果缺少建议提供的字段,请注明缺失项并继续进入阶段 2。

阶段 2 — 识别缺失项并请求其他来源
  1. 报告数据质量 — 打印摘要表,显示哪些字段已提供、哪些字段缺失。示例:

    Data Quality Report:
    • Course codes: 120/120 OK
    • Course names: 120/120 OK
    • Credit hours: 120/120 OK
    • Descriptions: 115/120 (5 missing)
    • Course types: 118/120 (2 missing)
    
  2. 请求补充来源 — 如果缺少关键字段(描述、类型),询问用户:“您是否有其他来源 — 网站、院系文档或映射文件?”

    • 如果有:接收其他来源,解析并与主要来源的数据合并
    • 如果没有:提出使用占位值创建课程,或停止并等待完整数据

    重复此步骤,直到填补所有缺失项,或用户确认使用不完整数据继续。

    如果学科到 Business Account 的映射存在歧义,请一次性解决所有歧义情况(来源文档 + 实时公开课程目录 + 课程级别/编号模式),并一并展示,而不是每次只处理一项(参见 references/gotchas.md)。

  3. 检测多组成部分课程 — 将课程代码传入讲座/实验课配对辅助工具:

    python3 scripts/detect_linked_courses.py --suffixes "L,LAB" <<'JSON'
    [ "BIO-201", "BIO-201L", ... ]
    JSON
    

    每个返回的 {"lecture", "lab"} 配对都是多组成部分课程的候选项。

    • 如果找到任何配对,请暂停并说明:“我找到了 X 门多组成部分课程(讲座 + 实验课)。请将其设置为 (1) 独立课程,还是 (2) 关联课程(IsLinkedOnly 标记,学生必须同时修读两门)?”

    如果用户选择“关联课程”,请索要映射文件,或确认命名模式可靠。

    检测授课形式重复项 — 按课程代码对解析出的行分组。同一代码但 Duration/DurationUnit/Description 不同 = 授课形式变体(例如在线与线下),而非重复项 — 绝不可静默去重/合并。每次运行只需确定一次如何表示,而非逐门课程确认。参见 references/large-catalog-handling.md §2。

阶段 3 — 批量创建 Learning 和 LearningCourse 记录
  1. 准备批量创建计划 — 将解析出的课程记录传入批处理辅助工具。Composite API 每次调用最多接受 200 条记录,但 Agent 工具调用负载的实际上限较低(约 56KB/150 条记录)。作为 Agent 执行时使用 --batch-size 25(只有在调用环境确认能处理更大的负载时,才调高至接近 200):

    python3 scripts/batch_courses.py --batch-size 25 <<'JSON'
    [ { "Name": "...", ... }, ... ]
    JSON
    

    返回数组中的每个元素都是一次 Composite API 调用所需的一组记录。打印:“正在开始导入课程目录。我将使用 Composite API 分批创建记录。总计:X 门课程。” 如果批次通过正常的调度传输仍无法内联提交,请使用更小的 --batch-size(例如 10)重新运行,而不是切换传输方式。在第 5 阶段摘要中分别记录并报告调度与回退的次数。

    超过 200 门课程(8 个以上批次):提前说明批次数量;每个批次完成后立即打印 Batch N complete 行(而不是等到最后);并且——仅当当前界面提供并行任务/子 Agent 工具时——遵循 references/large-catalog-handling.md §3-4 中的扇出规则。切勿假设存在此功能;默认按顺序处理批次。

  2. 检查是否已有课程 — 创建 Batch 1 前,仅检查本批次自己的课程代码,绝不执行全局查询(组织中已有无关的目录条目不构成冲突)。

    先拆分本批次的课程代码(scripts/split_course_code.py)——使用这些身份字段进行检查,绝不要使用 CipCode(许多组织会将其留空 — 参见 references/gotchas.md)。

    Headless-360 说明:SOQL /query 可通过 headless-360 调度工具路由(已通过实时测试确认)——查询 LearningCourse,精确匹配每个拆分出的 {SubjectAbbreviation, CourseNumber} 对(SOQL 不支持元组 IN,因此要用 OR 连接各对):SELECT Id, LearningId, Name, SubjectAbbreviation, CourseNumber FROM LearningCourse WHERE (SubjectAbbreviation = 'BIO' AND CourseNumber = '201') OR ...。如果 Id 未知且无法进行 SOQL 查询,请让用户提供 Id(或先前的导入清单)。绝不要通过 Bash/CLI 执行此操作 — 参见 references/gotchas.md。

    如果返回了任何行(即本批次中某个 SubjectAbbreviation+CourseNumber 对已存在于组织中),请暂停并说明冲突:

    • 并排展示现有课程详情与新导入数据
    • 检查现有课程是否存在依赖项(课程开设记录、选课记录)
    • 提供以下选项:(1) 更新现有课程,(2) 跳过现有课程,(3) 取消并手动审核

    等待用户决定后再继续。

  3. 创建 Batch 1 — 每门课程分两步处理:

    字段位置(已通过 describe 验证):CipCode、Duration、DurationUnit、ProviderId 只能在 Learning 上创建 — 在子对象上使用 ProviderId 会报错 INVALID_FIELD_FOR_INSERT_UPDATE。LearningCourse 使用 Name、LearningId、CourseType、IsLinkedOnly、SubjectAbbreviation、CourseNumber、Description、CourseLevelDescription、FieldOfStudy — 它通过 LearningId 继承账户关联。构建请求正文前,请使用 describe 确认。

    CipCode 为可选项。 这是联邦 CIP 分类体系字段,并非课程的身份标识 — 除非来源提供真实的 CIP 代码且用户确认,否则不要设置。

    步骤 1:通过 Composite API 创建 Learning(父级)记录:

    POST /services/data/vXX.X/composite/sobjects
    {
      "allOrNone": false,
      "records": [
        {
          "attributes": {"type": "Learning", "referenceId": "learning1"},
          "Name": "<course-name>",
          "Type": "LearningCourse",
          "Description": "<description>",
          "ProviderId": "<account-id>",
          "Duration": <hours>,
          "DurationUnit": "Credit Hours",
          "IsActive": true
        },
        ...
      ]
    }
    

    捕获每条返回的 Id(顺序与 records 数组一致),以便在步骤 2 中将其用作 LearningId。

    步骤 2:通过 Composite API 创建 LearningCourse(子级)记录:

    POST /services/data/vXX.X/composite/sobjects
    {
      "allOrNone": false,
      "records": [
        {
          "attributes": {"type": "LearningCourse"},
          "Name": "<course-record-name>",
          "LearningId": "<learning-id-from-step-1>",
          "SubjectAbbreviation": "<subject-prefix>",
          "CourseNumber": "<course-number>",
          "CourseType": "<parsed-course-type>",
          "IsLinkedOnly": false
        },
        ...
      ]
    }
    

    根据解析/映射出的课程类型为对应记录设置 CourseType(值映射见 Gotchas 表)——不要将其硬编码。实验课记录必须设置 CourseType: "Lab",而不是 "Lecture"。

    CourseType 选项列表值(已验证):Lecture、Lab、Exam、Drill、Recitation。

    restrictedPicklist: true 表示只有当前列出的值可提交,并不意味着选项集合是封闭的。 如果来源值(例如 DurationUnit = "Program Points")不在 describe 列表中,不要悄悄丢弃或重新映射它 — 请提供通过元数据添加选项列表值的方案,并同时给出重新映射或使用占位值的选项(参见 references/gotchas.md)。

    输出进度:Batch 1 complete — X Learning + X LearningCourse records created。

  4. 处理多组件课程 — 如果用户在步骤 6 中选择了“关联课程”:

    • 创建讲座课程,设置 CourseType: "Lecture"、IsLinkedOnly = false
    • 创建实验课程,设置 CourseType: "Lab"、IsLinkedOnly = true
    • 使用 detect_linked_courses.py 中的配对(或用户提供的映射文件)确定哪些实验课程仅供关联
    • 打印:X linked lab courses created (IsLinkedOnly = true)
  5. 处理剩余批次 — 对第 2 批、第 3 批等重复步骤 8-10,直到创建完所有课程。

阶段 4 — 数据质量验证与冲突解决
  1. 扫描不完整记录 — 所有批次完成后,根据创建响应中记录的 Id 读取每条新建记录:

    GET /services/data/vXX.X/sobjects/Learning/<id>
    GET /services/data/vXX.X/sobjects/LearningCourse/<id>
    

    Headless-360 说明:SOQL /query 可通过 headless-360 dispatch tool 路由(已通过实时测试确认)——CreatedDate = TODAY 查询可作为替代方案,无需逐一使用已记录的 Id 直接 GET 记录。

    分别对每个 sObject 运行完整性辅助脚本——CourseType 仅存在于 LearningCourse,Description 仅存在于 Learning;将两种结构合并到一次调用中会导致所有父记录都被误报为缺少 CourseType:

    python3 scripts/validate_completeness.py --required "Name" --recommended "Description" <<'JSON'
    [ { "Name": "...", "Description": "...", ... }, ... ]   # 仅包含 Learning 读取结果
    JSON
    

    使用 --recommended "CourseType" 对 LearningCourse 的读取结果重复执行。找出缺少描述的课程(Learning 报告)或缺少类型的课程(LearningCourse 报告)。打印数据质量报告(格式与步骤 4 相同)。

  2. 检测数据冲突 — 如果多个来源为同一门课程提供了不同值,请标记冲突,例如:“CHEM-301:网站显示 X,PDF 显示 Y——应使用哪个值?”等待用户决定,然后更新存在冲突的课程。

  3. 修复缺失项 — 如果用户提供了其他来源以补充缺失信息,请解析这些来源并更新不完整的课程:

    PATCH /services/data/vXX.X/sobjects/Learning/<id>
    {"Description": "..."}
    
    PATCH /services/data/vXX.X/sobjects/LearningCourse/<id>
    {"CourseType": "<parsed-course-type>"}
    
阶段 5 — 摘要与验证
  1. 生成导入摘要 — 打印最终报告:

    课程目录导入完成!
    摘要:
    • 已创建 X 条 Learning 记录和 X 条 LearningCourse 记录
    • Y 门独立实验课程,Z 门关联实验课程(IsLinkedOnly)
    • 所有课程均已关联到 <Business Account name>,并设为 Active
    使用的数据源:1. <primary> 2. <supplementary 1> 3. <supplementary 2>
    

    如果已从补充数据源补齐任何缺失数据(已执行步骤 5/14),最终报告还必须按以下顺序包含:

    • 数据质量报告 — 两张表格,分别为“初始数据(来自 <primary source>)”和“使用补充数据源后(<supplementary source>)”,每张表格都列出步骤 4 中的每个字段及其当前数量/总数。这是补齐前后的对照,而不仅是最终数量。
    • 缺失数据补齐记录 — 按字段分组(例如“已从 <source> 补齐缺少描述的课程”),用编号列表列出每门已补齐数据的课程及其获得的值。每个已补齐字段单独列一个列表。
  2. 提供验证选项 — 询问用户:“您是否希望我查询特定课程以验证数据?例如,我可以展示一门课程及其 LearningCourse 子记录,或检查是否还存在数据质量问题。”

  3. 检查当前运行用户能否实际查看刚创建的内容 — 不要只指出这一点,而是针对每个涉及的对象(Learning、LearningCourse,以及涉及 Person Account 时的 Account)执行检查(参见 references/gotchas.md 中 resolve-running-user + FLS/RecordTypeVisibility 模式)。指出任何缺失项及其对应的配置文件,提供具体的修复方案,并在写入 Profile/PermissionSet 元数据前等待确认。默认私有 OWD/共享设置适用于整个组织,而非单个用户 — 指出这一点,但不要提议更改。


规则 / 约束

约束原因
使用 Composite API,执行 Agent 时每次调用批量处理 25 条记录(API 最多支持 200 条)H360 MCP 中的 sObject REST 标准;包含 150 条记录的载荷(约 56 KB)会超过典型工具调用限制
先创建 Learning,再创建 LearningCourseLearningCourse.LearningId 要求父记录先存在
始终将 Learning.Type 设为 'LearningCourse'Learning 是多态对象 — Type 用于区分课程和项目
创建前始终查询是否已有课程防止重复;让用户决定如何处理冲突
对包含多个组成部分的课程暂停并请求澄清IsLinkedOnly 标记会影响选课行为 — 用户必须确认课程结构
继续之前报告数据质量缺失项让用户在提交到组织之前提供完整数据
对冲突数据等待用户决定Agent 无法判断哪个来源具有权威性
不要编造缺失数据占位值必须获得用户明确批准
仅在父级 Learning 上设置 ProviderId(Business Account)可在 Learning 上创建,不可在 LearningCourse 上创建 — 参见步骤 9 中的字段位置说明
默认将所有课程设为 Active 状态(IsActive = true)课程必须处于 Active 状态,才能显示在目录搜索/注册中
对组织调用使用 sObject REST API,不要使用 CLI 命令H360 MCP 技能没有用于连接组织的执行环境。scripts/ 辅助脚本仅用于本地数据转换(不调用组织)— 如果没有可用的 Bash 工具,请直接执行等效检查,不要跳过
使用与工具无关的措辞该技能将用于 Claude Code 之外的多个 AI 界面
超过 200 门课程或 20+ 个源页面时:遵循 references/large-catalog-handling.md,不要临时分块处理临时分块处理可能导致页面数据拆分丢失或重复创建

注意事项

已知故障模式及解决方案(解析问题、缺少 Business Account、选项列表映射、Composite 部分失败等)请参见 references/gotchas.md。


输出预期

交付内容:

  • 创建 X 条 Learning 记录(Type = 'LearningCourse')
  • 创建 X 条 LearningCourse 子记录(LearningId 查找关系)
  • 数据质量报告,显示每个字段的完整度
  • 导入摘要,列出所有使用的数据源
  • 可选:通过直接读取记录或 SOQL /query 验证课程(两者均可通过 headless-360 路由)

不会生成任何文件——所有输出都是通过 sObject REST API 创建的组织记录。


跨技能集成

需求委派给
手动创建单个 Learning Course平台元数据生成技能
创建 Learning Programs暂无专用技能——请询问用户
设置课程章节/开课安排暂无专用技能——请询问用户
配置选课资格规则暂无专用技能——请询问用户

参考文件索引

  • references/gotchas.md — 已知故障模式及其解决方法
  • references/gotchas-detail.md — 低频故障注意事项,由 gotchas.md 链接
  • references/large-catalog-handling.md — 适用于 >200 门课程或 >20 页源文档的规则:分页读取、授课形式重复检测、检查点、受控并行扇出(步骤 2、6、7)
  • scripts/resolve_api_version.py — 从 headless-360 端点映射中提取组织的最高 API 版本(执行传输)
  • scripts/validate_completeness.py — 必填/推荐字段存在性检查(步骤 3、12)
  • scripts/detect_linked_courses.py — 讲座/实验课程代码配对(步骤 6)
  • scripts/detect_modality_variants.py — 标记在 Duration/DurationUnit/Description 上存在差异的 CourseCode 行,供 large-catalog-handling.md §2 使用
  • scripts/batch_courses.py — Composite API 批次拆分(步骤 7)
  • scripts/split_course_code.py — SubjectAbbreviation/CourseNumber 拆分(步骤 9)

评论

支持 Markdown