Procházet zdrojové kódy

Merge branch 'pending' into contrib/mysy

星河欲转 před 1 týdnem
rodič
revize
6c18fd06b2
4 změnil soubory, kde provedl 470 přidání a 31 odebrání
  1. 6 1
      index/root_index.yaml
  2. 9 0
      resources/MKU/adapters.yaml
  3. 397 0
      resources/MKU/mku.js
  4. 58 30
      resources/YANGTZEU/yu.js

+ 6 - 1
index/root_index.yaml

@@ -122,6 +122,11 @@ schools:
     name: "绵阳师范学院"
     name: "绵阳师范学院"
     initial: "M"
     initial: "M"
     resource_folder: "MYSY"
     resource_folder: "MYSY"
+    
+  - id: "MKU"
+    name: "闽南科技学院"
+    initial: "M"
+    resource_folder: "MKU"
 
 
   - id: "SWJTU"
   - id: "SWJTU"
     name: "西南交通大学"
     name: "西南交通大学"
@@ -997,7 +1002,7 @@ schools:
     name: "安徽电气工程职业技术学院"
     name: "安徽电气工程职业技术学院"
     initial: "A"
     initial: "A"
     resource_folder: "AEPU"
     resource_folder: "AEPU"
-    
+
   - id: "SUSTECH"
   - id: "SUSTECH"
     name: "南方科技大学"
     name: "南方科技大学"
     initial: "N"
     initial: "N"

+ 9 - 0
resources/MKU/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/MKU/adapters.yaml
+adapters:
+  - adapter_id: "MKU_01"
+    adapter_name: "闽南科技学院教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "mku.js"
+    import_url: "https://jwgl.mku.edu.cn/"
+    maintainer: "Mutx163"
+    description: "适配闽南科技学院强智教务系统(新版 layui UI)。登录教务系统后运行,可选学期导入\"学期理论课表\",自动附带节次时间模板与学期配置(当前学期自动识别开学日期,历史学期请在 App 内自行设置)。"

+ 397 - 0
resources/MKU/mku.js

@@ -0,0 +1,397 @@
+// 闽南科技学院(mku.edu.cn) 轻屿课表适配脚本
+// 教务平台:强智教务系统(新版 layui UI)
+// 数据来源:个人课表 /jsxsd/xskb/xskb_list.do(默认渲染当前学期,可用 xnxq01id 切换学期)
+// 解析逻辑参考本仓库 HYNU / ZHKU 两个强智适配的实现,按 MKU 新版 DOM 重写
+// 非该校在校开发者适配,出现问题请提交 issue 或 PR
+
+// ===== 基础常量 =====
+
+const MKU_BASE_URL = "https://jwgl.mku.edu.cn";
+const MKU_TIMETABLE_URL = MKU_BASE_URL + "/jsxsd/xskb/xskb_list.do";
+const MKU_WEEK_AJAX_URL = MKU_BASE_URL + "/jsxsd/xskb/jxzlzc_xnxq_ajax";
+const MKU_HOME_URL = MKU_BASE_URL + "/jsxsd/framework/xsMainV.htmlx";
+
+// 默认总周数;实际值优先取自教务周次接口 jxzlzc_xnxq_ajax
+const MKU_DEFAULT_TOTAL_WEEKS = 20;
+
+// 每小节课 45 分钟、课间 10 分钟(与课表页大节时间 08:00~09:40 等标注一致)
+const MKU_SECTION_MINUTES = 45;
+const MKU_BREAK_MINUTES = 10;
+
+// 兜底作息(按 2026-2027-1 学期课表页标注的大节时间推导)
+const MKU_FALLBACK_TIME_SLOTS = [
+  { number: 1, startTime: "08:00", endTime: "08:45" },
+  { number: 2, startTime: "08:55", endTime: "09:40" },
+  { number: 3, startTime: "10:00", endTime: "10:45" },
+  { number: 4, startTime: "10:55", endTime: "11:40" },
+  { number: 5, startTime: "14:00", endTime: "14:45" },
+  { number: 6, startTime: "14:55", endTime: "15:40" },
+  { number: 7, startTime: "15:50", endTime: "16:35" },
+  { number: 8, startTime: "16:45", endTime: "17:30" },
+  { number: 9, startTime: "18:30", endTime: "19:15" },
+  { number: 10, startTime: "19:25", endTime: "20:10" },
+  { number: 11, startTime: "20:20", endTime: "21:05" },
+  { number: 12, startTime: "21:15", endTime: "22:00" }
+];
+
+// ===== 通用工具 =====
+
+// HH:mm 转当天分钟数
+function mkuTimeToMinutes(hhmm) {
+  const parts = hhmm.split(":");
+  return Number(parts[0]) * 60 + Number(parts[1]);
+}
+
+// 当天分钟数转 HH:mm
+function mkuMinutesToTime(total) {
+  const h = Math.floor(total / 60);
+  const m = total % 60;
+  return String(h).padStart(2, "0") + ":" + String(m).padStart(2, "0");
+}
+
+// Date 转 YYYY-MM-DD
+function mkuFormatDate(date) {
+  const y = date.getFullYear();
+  const m = String(date.getMonth() + 1).padStart(2, "0");
+  const d = String(date.getDate()).padStart(2, "0");
+  return y + "-" + m + "-" + d;
+}
+
+// ===== 周次与节次解析 =====
+
+// 将周次片段("1,3,5" / "1-16" / "1-8,10-16")展开为去重排序的数字数组
+function mkuExpandWeeks(weekPart) {
+  const weeks = [];
+  (weekPart.match(/\d+(?:\s*[-~]\s*\d+)?/g) || []).forEach((seg) => {
+    const m = seg.match(/(\d+)\s*(?:[-~]\s*(\d+))?/);
+    const start = Number(m[1]);
+    const end = m[2] ? Number(m[2]) : start;
+    for (let w = start; w <= end; w++) weeks.push(w);
+  });
+  return [...new Set(weeks)].sort((a, b) => a - b);
+}
+
+// 解析"时间"字段,如 "1,3,5,7,9,11,13,15周[1-2节]"
+function mkuParseTimeText(timeText) {
+  if (!timeText) return null;
+  const weekMatch = timeText.match(/^([^周]+)周/);
+  const secMatch = timeText.match(/\[\s*(\d+)\s*(?:[-~]\s*(\d+))?\s*节\s*\]/);
+  if (!weekMatch && !secMatch) return null;
+
+  let weeks = weekMatch ? mkuExpandWeeks(weekMatch[1]) : null;
+  if (weeks) {
+    // 兼容"单周/双周"标注
+    if (/单\s*周/.test(timeText)) weeks = weeks.filter((w) => w % 2 === 1);
+    if (/双\s*周/.test(timeText)) weeks = weeks.filter((w) => w % 2 === 0);
+  }
+
+  return {
+    weeks,
+    startSection: secMatch ? Number(secMatch[1]) : null,
+    endSection: secMatch ? (secMatch[2] ? Number(secMatch[2]) : Number(secMatch[1])) : null
+  };
+}
+
+// ===== 课表解析 =====
+
+// 将表格展开为 grid[row][col],正确处理 rowspan / colspan
+function mkuBuildGrid(table) {
+  const grid = [];
+  Array.from(table.rows).forEach((tr, r) => {
+    if (!grid[r]) grid[r] = [];
+    let col = 0;
+    Array.from(tr.cells).forEach((td) => {
+      while (grid[r][col]) col++;
+      const rowSpan = td.rowSpan || 1;
+      const colSpan = td.colSpan || 1;
+      for (let dr = 0; dr < rowSpan; dr++) {
+        for (let dc = 0; dc < colSpan; dc++) {
+          if (!grid[r + dr]) grid[r + dr] = [];
+          grid[r + dr][col + dc] = td;
+        }
+      }
+      col += colSpan;
+    });
+  });
+  return grid;
+}
+
+// 解析行标签(第 0 列):大节范围与时间,如 "1-2" + "08:00~09:40"
+function mkuParseRowInfos(table) {
+  const rowInfos = new Map();
+  Array.from(table.rows).forEach((tr, r) => {
+    const labelCell = tr.cells[0];
+    if (!labelCell) return;
+    const titleText = labelCell.querySelector(".index-title")
+      ? labelCell.querySelector(".index-title").textContent.trim()
+      : "";
+    const range = titleText.match(/^(\d+)(?:\s*[-~]\s*(\d+))?$/);
+    if (!range) return;
+    const timeMatch = labelCell.textContent.match(/(\d{1,2}:\d{2})\s*~\s*(\d{1,2}:\d{2})/);
+    rowInfos.set(r, {
+      startSection: Number(range[1]),
+      endSection: range[2] ? Number(range[2]) : Number(range[1]),
+      blockStart: timeMatch ? mkuTimeToMinutes(timeMatch[1]) : null,
+      blockEnd: timeMatch ? mkuTimeToMinutes(timeMatch[2]) : null
+    });
+  });
+  return rowInfos;
+}
+
+// 从"老师:xx;时间:..周[..节];地点:yy"详情串中按标签取字段
+function mkuParseInfoField(text, label) {
+  const m = text.match(new RegExp(label + "[::]\\s*([^;;]*)"));
+  return m ? m[1].trim() : "";
+}
+
+// 解析课表文档中的全部课程(同一单元格可能含多条课程,逐 li 解析)
+function mkuParseCourses(doc) {
+  const sampleTd = doc.querySelector('td[name="kbDataTd"]');
+  if (!sampleTd) return { courses: [], rowInfos: new Map() };
+  const table = sampleTd.closest("table");
+  const grid = mkuBuildGrid(table);
+  const rowInfos = mkuParseRowInfos(table);
+
+  const courses = [];
+  const seenTds = new Set();
+
+  for (const [row, rowInfo] of rowInfos) {
+    for (let day = 1; day <= 7; day++) {
+      const td = grid[row] && grid[row][day];
+      // rowspan 跨行的大格只在首行解析一次
+      if (!td || seenTds.has(td)) continue;
+      seenTds.add(td);
+
+      Array.from(td.querySelectorAll("li")).forEach((li) => {
+        const nameEl = li.querySelector(".qz-hasCourse-title");
+        const infoEl = li.querySelector(".qz-hasCourse-abbrinfo");
+        if (!nameEl || !infoEl) return;
+        const name = nameEl.textContent.trim();
+        const infoText = infoEl.textContent.trim();
+        if (!name || !infoText) return;
+
+        const teacher = mkuParseInfoField(infoText, "老师") || "未知教师";
+        const position = mkuParseInfoField(infoText, "地点") || "未知地点";
+        const timeInfo = mkuParseTimeText(mkuParseInfoField(infoText, "时间"));
+
+        // 节次以"时间"字段为准,缺失时回退到大节行标签
+        const startSection = timeInfo && timeInfo.startSection
+          ? timeInfo.startSection
+          : rowInfo.startSection;
+        const endSection = timeInfo && timeInfo.endSection
+          ? timeInfo.endSection
+          : rowInfo.endSection;
+        const weeks = timeInfo && timeInfo.weeks && timeInfo.weeks.length
+          ? timeInfo.weeks
+          : null;
+        // 无周次信息则跳过,避免导入错误数据
+        if (!weeks) return;
+
+        courses.push({
+          name,
+          teacher,
+          position,
+          day,
+          startSection,
+          endSection,
+          weeks
+        });
+      });
+    }
+  }
+
+  // 完全重复的条目去重(同名/同师/同地/同天/同节次/同周次)
+  const deduped = [];
+  const seenKeys = new Set();
+  for (const course of courses) {
+    const key = [
+      course.name, course.teacher, course.position, course.day,
+      course.startSection, course.endSection, course.weeks.join(",")
+    ].join("|");
+    if (seenKeys.has(key)) continue;
+    seenKeys.add(key);
+    deduped.push(course);
+  }
+
+  return { courses: deduped, rowInfos };
+}
+
+// 由课表页大节时间推导小节作息;推导失败时回退兜底配置
+function mkuBuildTimeSlots(rowInfos) {
+  const slots = [];
+  const rows = [...rowInfos.values()].sort((a, b) => a.startSection - b.startSection);
+  for (const info of rows) {
+    if (info.blockStart == null || info.blockEnd == null) return MKU_FALLBACK_TIME_SLOTS;
+    const count = info.endSection - info.startSection + 1;
+    const span = info.blockEnd - info.blockStart;
+    if (span !== MKU_SECTION_MINUTES * count + MKU_BREAK_MINUTES * (count - 1)) {
+      return MKU_FALLBACK_TIME_SLOTS;
+    }
+    for (let i = 0; i < count; i++) {
+      const start = info.blockStart + i * (MKU_SECTION_MINUTES + MKU_BREAK_MINUTES);
+      slots.push({
+        number: info.startSection + i,
+        startTime: mkuMinutesToTime(start),
+        endTime: mkuMinutesToTime(start + MKU_SECTION_MINUTES)
+      });
+    }
+  }
+  return slots.length ? slots : MKU_FALLBACK_TIME_SLOTS;
+}
+
+// ===== 教务数据获取 =====
+
+// 拉取个人课表页面(不传学期则渲染教务当前学期)
+async function mkuFetchTimetableDoc(semesterId) {
+  let url = MKU_TIMETABLE_URL + "?viweType=0&zc=&cj0701id=";
+  if (semesterId) url += "&xnxq01id=" + encodeURIComponent(semesterId);
+  const resp = await fetch(url, { credentials: "include" });
+  if (!resp.ok) throw new Error("获取课表页面失败:HTTP " + resp.status);
+  const html = await resp.text();
+  const doc = new DOMParser().parseFromString(html, "text/html");
+  if (!doc.querySelector("#xnxq01id") && !doc.querySelector('td[name="kbDataTd"]')) {
+    throw new Error("未获取到课表数据,请先在浏览器登录教务系统");
+  }
+  return doc;
+}
+
+// 学期下拉选项(value 与显示文本一致,如 2026-2027-1)
+function mkuGetSemesterOptions(doc) {
+  const select = doc.querySelector("#xnxq01id");
+  if (!select) return [];
+  return Array.from(select.options)
+    .filter((o) => o.value)
+    .map((o) => ({ value: o.value, text: o.text.trim(), selected: o.selected }));
+}
+
+// 学期总周数(接口返回如 [{"qszc":1,"jszc":20}])
+async function mkuFetchTotalWeeks(semesterId) {
+  try {
+    const resp = await fetch(MKU_WEEK_AJAX_URL + "?xnxq01id=" + encodeURIComponent(semesterId), {
+      credentials: "include"
+    });
+    const data = await resp.json();
+    if (Array.isArray(data) && data.length > 0 && Number(data[0].jszc) > 0) {
+      return Number(data[0].jszc);
+    }
+  } catch (error) {
+    console.warn("获取学期总周数失败,使用默认值:", error);
+  }
+  return MKU_DEFAULT_TOTAL_WEEKS;
+}
+
+// 学期第一周周一日期:仅当所选学期是教务"当前学期"时可信
+// 首页 #headerShowTime 形如 "2026-2027-1 第1周",结合本地今天日期反推开学日
+async function mkuFetchSemesterStartDate(semesterId) {
+  try {
+    const resp = await fetch(MKU_HOME_URL, { credentials: "include" });
+    if (!resp.ok) return null;
+    const html = await resp.text();
+    const m = html.match(/id="headerShowTime"[^>]*>\s*([^<]+?)\s*</);
+    if (!m) return null;
+    const currentSemester = m[1].split(/\s+/)[0];
+    const weekMatch = m[1].match(/第\s*(\d+)\s*周/);
+    if (currentSemester !== semesterId || !weekMatch) return null;
+    const currentWeek = Number(weekMatch[1]);
+    if (!(currentWeek >= 1)) return null;
+
+    const now = new Date();
+    const mondayOffset = (now.getDay() + 6) % 7; // 周一记 0
+    const start = new Date(
+      now.getFullYear(),
+      now.getMonth(),
+      now.getDate() - mondayOffset - (currentWeek - 1) * 7
+    );
+    return mkuFormatDate(start);
+  } catch (error) {
+    console.warn("获取学期开始日期失败:", error);
+    return null;
+  }
+}
+
+// ===== 主流程 =====
+
+async function mkuRunImportFlow() {
+  const bridge = window.shiguangBridge;
+  const bridgePromise = window.shiguangBridgePromise;
+
+  const confirmed = await bridgePromise.showAlert(
+    "导入说明",
+    "将读取闽南科技学院教务系统\"个人课表\"数据并导入轻屿课表。\n请确保已在当前浏览器登录教务系统(jwgl.mku.edu.cn)。\n是否继续?",
+    "确认已登录"
+  );
+  if (!confirmed) {
+    bridge.showToast("导入已取消");
+    return;
+  }
+
+  bridge.showToast("正在读取课表页面...");
+  const firstDoc = await mkuFetchTimetableDoc();
+
+  // 让用户选择学期(默认为教务当前学期)
+  const options = mkuGetSemesterOptions(firstDoc);
+  const defaultOption = options.find((o) => o.selected) || null;
+  let semesterId = defaultOption ? defaultOption.value : null;
+  if (options.length > 0) {
+    const defaultIndex = Math.max(0, options.findIndex((o) => o.selected));
+    const labels = options.map((o) => o.text + (o.selected ? "(当前学期)" : ""));
+    const index = await bridgePromise.showSingleSelection(
+      "选择要导入的学期",
+      JSON.stringify(labels),
+      defaultIndex
+    );
+    if (index == null || index < 0 || index >= options.length) {
+      bridge.showToast("导入已取消");
+      return;
+    }
+    semesterId = options[index].value;
+  }
+
+  // 所选学期与默认渲染学期一致时直接复用首次页面
+  const doc = defaultOption && semesterId === defaultOption.value
+    ? firstDoc
+    : await mkuFetchTimetableDoc(semesterId);
+
+  const { courses, rowInfos } = mkuParseCourses(doc);
+  if (courses.length === 0) {
+    bridge.showToast("未解析到课程,请确认所选学期有课且已登录");
+    return;
+  }
+
+  bridge.showToast("正在获取学期配置...");
+  const totalWeeks = await mkuFetchTotalWeeks(semesterId);
+  const startDate = await mkuFetchSemesterStartDate(semesterId);
+
+  const timeSlots = mkuBuildTimeSlots(rowInfos);
+  const config = {
+    defaultClassDuration: MKU_SECTION_MINUTES,
+    defaultBreakDuration: MKU_BREAK_MINUTES,
+    semesterTotalWeeks: totalWeeks,
+    firstDayOfWeek: 1
+  };
+  // 仅当前学期能可靠推算开学日期,历史学期留给 App 端处理
+  if (startDate) config.semesterStartDate = startDate;
+
+  await bridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
+  await bridgePromise.saveCourseConfig(JSON.stringify(config));
+  await bridgePromise.saveImportedCourses(JSON.stringify(courses));
+
+  bridge.showToast("导入成功:共 " + courses.length + " 条课程");
+  bridge.notifyTaskCompletion();
+}
+
+// 启动导入流程
+(async () => {
+  try {
+    await mkuRunImportFlow();
+  } catch (error) {
+    console.error("课表导入失败:", error);
+    try {
+      window.shiguangBridge.showToast(
+        "导入失败:" + (error && error.message ? error.message : error)
+      );
+    } catch (e) {
+      /* 桥接不可用时仅保留控制台日志 */
+    }
+  }
+})();

+ 58 - 30
resources/YANGTZEU/yu.js

@@ -158,7 +158,29 @@
         return args;
         return args;
     }
     }
 
 
-    // 从脚本文本中的 TaskActivity 还原课程
+    // 读取构造参数,避免教师表达式或课程名中的括号提前结束匹配。
+    function readTaskActivityArgs(text, start) {
+        let depth = 1;
+        let quote = "";
+        let escaped = false;
+        for (let i = start; i < text.length; i++) {
+            const ch = text[i];
+            if (quote) {
+                if (escaped) escaped = false;
+                else if (ch === "\\") escaped = true;
+                else if (ch === quote) quote = "";
+                continue;
+            }
+            if (ch === "\"" || ch === "'") quote = ch;
+            else if (ch === "(") depth++;
+            else if (ch === ")" && --depth === 0) {
+                return { argsText: text.slice(start, i), end: i + 1 };
+            }
+        }
+        return null;
+    }
+
+    // 每个 TaskActivity 可以分配到多个节次,先划分课程声明,再读取其全部 index。
     function parseCoursesFromTaskActivityScript(htmlText) {
     function parseCoursesFromTaskActivityScript(htmlText) {
         const text = String(htmlText || "");
         const text = String(htmlText || "");
         if (!text) return [];
         if (!text) return [];
@@ -166,46 +188,52 @@
         const unitCount = unitCountMatch ? parseInt(unitCountMatch[1], 10) : 0;
         const unitCount = unitCountMatch ? parseInt(unitCountMatch[1], 10) : 0;
         if (!Number.isInteger(unitCount) || unitCount <= 0) return [];
         if (!Number.isInteger(unitCount) || unitCount <= 0) return [];
         const courses = [];
         const courses = [];
-        const blockRe = /activity\s*=\s*new\s+TaskActivity\(([^]*?)\)\s*;\s*index\s*=\s*(?:(\d+)\s*\*\s*unitCount\s*\+\s*(\d+)|(\d+))\s*;\s*table\d+\.activities\[index\]/g;
+        const activities = [];
+        const activityRe = /\bactivity\s*=\s*new\s+TaskActivity\s*\(/g;
         let match;
         let match;
-        while ((match = blockRe.exec(text)) !== null) {
-            const argsText = match[1] || "";
-            const args = splitJsArgs(argsText);
+        while ((match = activityRe.exec(text)) !== null) {
+            const call = readTaskActivityArgs(text, activityRe.lastIndex);
+            if (!call) continue;
+            activities.push({ ...call, start: match.index });
+            activityRe.lastIndex = call.end;
+        }
+        for (let i = 0; i < activities.length; i++) {
+            const activity = activities[i];
+            const args = splitJsArgs(activity.argsText);
             if (args.length < 7) continue;
             if (args.length < 7) continue;
-            const dayPart = match[2];
-            const sectionPart = match[3];
-            const directIndexPart = match[4];
-            let indexValue = -1;
-            if (dayPart != null && sectionPart != null) {
-                indexValue = parseInt(dayPart, 10) * unitCount + parseInt(sectionPart, 10);
-            } else if (directIndexPart != null) {
-                indexValue = parseInt(directIndexPart, 10);
-            }
-            if (!Number.isInteger(indexValue) || indexValue < 0) continue;
-            const day = Math.floor(indexValue / unitCount) + 1;
-            let section = (indexValue % unitCount) + 1;
-            section = mapSectionToTimeSlotNumber(section);
-            if (day < 1 || day > 7 || section < 1 || section > 16) continue;
             let teacher = unquoteJsLiteral(args[1]);
             let teacher = unquoteJsLiteral(args[1]);
             // 教师值为 JS 表达式时,尝试解析或置空以触发兜底
             // 教师值为 JS 表达式时,尝试解析或置空以触发兜底
             if (teacher && /\.join\s*\(/.test(teacher)) {
             if (teacher && /\.join\s*\(/.test(teacher)) {
-                const resolved = resolveTeachersForTaskActivityBlock(text, match.index, teacher);
+                const resolved = resolveTeachersForTaskActivityBlock(text, activity.start, teacher);
                 teacher = resolved || "";
                 teacher = resolved || "";
             }
             }
             const name = cleanCourseName(unquoteJsLiteral(args[3]));
             const name = cleanCourseName(unquoteJsLiteral(args[3]));
-            let position = unquoteJsLiteral(args[5]);
+            const position = unquoteJsLiteral(args[5]);
             const weekBitmap = unquoteJsLiteral(args[6]);
             const weekBitmap = unquoteJsLiteral(args[6]);
             const weeks = normalizeWeeks(parseValidWeeksBitmap(weekBitmap));
             const weeks = normalizeWeeks(parseValidWeeksBitmap(weekBitmap));
             if (!name) continue;
             if (!name) continue;
-            courses.push({
-                name,
-                teacher,
-                position,
-                day,
-                startSection: section,
-                endSection: section,
-                weeks
-            });
+            const end = i + 1 < activities.length ? activities[i + 1].start : text.length;
+            const assignments = text.slice(activity.end, end);
+            const indexRe = /\bindex\s*=\s*(?:(\d+)\s*\*\s*unitCount\s*\+\s*(\d+)|(\d+))\s*;\s*table\d+\.activities\[index\]/g;
+            let indexMatch;
+            while ((indexMatch = indexRe.exec(assignments)) !== null) {
+                const indexValue = indexMatch[3] != null
+                    ? parseInt(indexMatch[3], 10)
+                    : parseInt(indexMatch[1], 10) * unitCount + parseInt(indexMatch[2], 10);
+                if (!Number.isInteger(indexValue) || indexValue < 0) continue;
+                const day = Math.floor(indexValue / unitCount) + 1;
+                const section = mapSectionToTimeSlotNumber((indexValue % unitCount) + 1);
+                if (day < 1 || day > 7 || section < 1 || section > 16) continue;
+                courses.push({
+                    name,
+                    teacher,
+                    position,
+                    day,
+                    startSection: section,
+                    endSection: section,
+                    weeks
+                });
+            }
         }
         }
         return mergeContiguousSections(courses);
         return mergeContiguousSections(courses);
     }
     }