Install
openclaw skills install @ez-hq/uni-course-schedulerAI-powered university course planning assistant. Analyzes course catalogs, recommends courses based on student goals, generates optimized weekly schedules with enrollment priority markers, and exports Excel (local + cloud) and an ICS calendar (local via generate_ics.py, or cloud standard mode). Supports international education systems (US/AU/UK/ECTS/CN). Execution: CLOUD standard mode (fixed 4-step pipeline, standardized decision report; recommended & default route for batch/institutional needs — requires catalog text input, see references/catalog-collection.md). Two cloud platforms by payment method: Shengsuanyun (CNY ¥0.5/run, China payment) or CogFoundry (USD $0.10/run, international credit card). LOCAL personal lightweight version (free, single-school single-student only, no batch pipeline, no standardized decision report).
openclaw skills install @ez-hq/uni-course-schedulerHelp university students plan courses, build weekly timetables, and export Excel (local + cloud) and ICS calendar files (local or cloud mode).
openpyxl and icalendar packagespip install "openpyxl>=3.1,<3.2" "icalendar>=5,<6" "pytz==2024.2"
Prefer a hash-locked requirements.txt + pip install --require-hashes for production use.Ask questions one at a time, in this exact order. Wait for each answer before proceeding.
Ask (in the user's language): "What is the name of your university?" Note the university name for course catalog research.
Ask (in the user's language): "Which country is your university in, and what education/credit system does it use?" Common systems: US (credits), AU (UoC/credit points), UK (CATS), ECTS, CN (credits). This determines credit calculation and semester structure.
Ask (in the user's language): "What is your program / major?" Use this to filter relevant courses from the catalog.
Ask (in the user's language): "What year level are you in? When do you start?" Examples: Year 1 Term 1 2027, Year 2 Semester 1, etc. Also ask about special calendar systems (trimester, quarter, etc.).
Year Level Enum Mapping (MANDATORY — map the user's academic context to the cloud year_level enum):
The cloud pipeline only accepts these exact values for year_level. Always map what the user says to one of them:
"Year 1" / "Year 2" / "Year 3" / "Year 4""Master""Year 1"–"Year 4" for undergraduate-track, "Master" for postgraduate-track)"Year 1", "Year 2", "Year 3", "Year 4", or "Master".Ask (in the user's language): "How would you like me to help with course selection?"
If Mode A, also ask:
If Mode A, present the two execution modes and ask the user to choose. Batch / multi-school / decision report needed → use Cloud Standard Mode.
Ask (in the user's language): "There are two execution modes. Choose the first one (Cloud Standard Mode) for batch / multi-school / standardized decision report needs."
references/catalog-collection.md), or the buyer pastes the official handbook text manually. Fuller catalog = more reliable output.https://loomloom.shengsuanyun.com/loom/v1, token LOOMLOOM_TOKEN_SHENGSUANYUN, fixed fee ¥0.5/run.https://loomloom.cogfoundry.ai/loom/v1, token LOOMLOOM_TOKEN_COGFOUNDRY, fixed fee $0.10/run.LOOMLOOM_SERVER (and use the matching token env var) for all subsequent cloud commands.loomloom market quote/run); only the server/token differs by platform.Execution routing (capability-based, user-informed): Decide by what the user actually needs, then explain the choice and, where it involves a paid or external call, get explicit consent. Never silently pick a paid route, and never force Cloud on a user who can be served locally.
Cloud consent & data disclosure (mandatory, before the first Cloud submission per user): In the user's own language, before invoking any paid Cloud call, clearly state:
Cloud mode rules (mandatory, when the user explicitly chooses it):
references/catalog-collection.md: use
WebSearch / WebFetch to collect the university's official catalog text, pass the
quality gate (≥500 chars, ≥5 real courses, 6-step search protocol below), and
provide it as the course_catalog input. Never submit a cloud run with an
empty/failed catalog — it produces placeholder output and wastes the user's money.
If collection fails, ask the user to paste their official program-handbook text instead.1a. Pre-flight catalog validation (MANDATORY — before quote, before confirmation):
After collecting the catalog but BEFORE running loomloom market quote, run this
checklist and do NOT proceed until ALL items pass:
```
[COURSE DATA]
✅ Course codes: every course has a unique code (e.g. BIOL1020); 100% coverage of target courses
✅ Credits: every course has a credit value; missing credits → cannot submit
✅ Offering semester: every course lists its offering (S1/S2/Summer, etc.)
✅ Term dates: semester_start + num_teaching_weeks present
[TIMETABLE DATA — two sub-checks, both required]
✅ Timetable presence: TIMETABLE section exists, and every target course has at least 1 session slot
(new courses like BIOM1001 may be exempt, marked TBC / to verify)
✅ Timetable completeness: total TIMETABLE entries ≥ 2 × target course count
(typical science courses need 2-3 slots each: lecture + lab/practical/tutorial)
If the completeness check FAILs: likely only lectures were given, missing labs/tutorials
```
**How to check timetable data in the catalog**: the `course_catalog` text must contain
lines like `BIOL1020 | Lecture | Monday | 10:00-11:00 | weeks 1-13` for each course.
If the catalog has course codes, credits, and descriptions but NO timetable lines,
the pre-flight validation FAILS.
**Timetable data format (must be in the catalog text)**:
```
TIMETABLE:
BIOL1020 | Lecture | Monday | 10:00-11:00 | weeks 1-13
BIOL1020 | Tutorial | Wednesday | 10:00-11:00 | weeks 1-13
BIOL1020 | Practical | Friday | 09:00-12:00 | weeks 1-12
CHEM1100 | Lecture | Monday | 14:00-15:00 | weeks 1-13
CHEM1100 | Lab | Thursday | 13:00-16:00 | weeks 2,4,6,8,10,12
```
Each line: `course_code | session_type | day | start_time-end_time | week_range`
**If pre-flight validation fails:**
- **Case A: no TIMETABLE section, or completeness check FAILs**:
→ **Step 1: check whether the timetable has been published**
WebSearch `<university> timetable <semester_year>` (e.g. `UQ timetable 2027 semester 1`)
If the 2027 timetable is not yet published (common: UQ publishes in Nov-Dec each year):
→ Tell the user: "The official 2027 timetable is not published yet (expected Nov-Dec 2026).
I can fill in the catalog with typical prior-year timetable times first, so the Cloud-run timetable structure is complete
(including labs/tutorials), but exact times must be verified on mySI-net once the official timetable is out.
Do you want me to do that now? Or run Cloud first and fill in locally afterwards?"
→ If the user picks "use prior-year data": search for the same-period timetable from previous years
(search `<course_code> <university> timetable 2026`) and fill TIMETABLE rows with historical data.
and mark each row's notes with "2026 reference time — verify when the 2027 timetable is released".
→ If the user picks "run Cloud first": allow it, mark default_timetable=true, ensure post-processing fills in.
→ **Step 2: search for timetable data**
(only when the 2027 timetable is already published)
Use WebSearch/WebFetch to find timetable data
(search `<course_code> <university> timetable 2027`), append TIMETABLE rows to
the end of the catalog text. Re-run the precheck.
→ If search fails: "I couldn't find timetable data. I can fill in typical times from previous years, or run Cloud first — which do you prefer?"
→ **If the user says "never mind" (or search failed and the user refuses historical data)**:
Warn explicitly: "OK, but note: without timetable data, Cloud will only output a default timetable of 1-2 sessions per course,
labs/tutorials will not appear. I will fill in the timetable locally after the Cloud run."
→ If the user insists: allow it, but ensure post-processing (rule 7) fills in the timetable.
- **Case B: invalid JSON format**:
→ Check whether catalog_text contains invalid JSON characters (non-printable chars, unescaped quotes, etc.)
→ If invalid: fix it before submitting. Never submit malformed JSON.
After pre-flight passes, validate the submission JSON file:
Before running loomloom market quote --input-file <request.json>:
[SUBMISSION JSON VALIDATION] ✅ File exists: request.json exists and is readable ✅ Valid JSON syntax: python -c "import json; json.load(open('request.json'))" ✅ Required fields: university_name, education_system, major, year_level, planning_mode, student_goal, course_catalog, semester_info all present and non-empty ✅ chosen_courses handling: only fill the course-code array when the user has already decided (user_decided); otherwise OMIT the field — never write null (the platform rejects "field: null is not supported") ✅ Valid enum values: education_system is one of the listed enum values (Australian Credit Points, etc.) ✅ No oversized fields: every field value < 50000 chars (to avoid pipeline truncation) ✅ UTF-8 encoding: file is valid UTF-8, no BOM, no corrupted characters
If any check fails: fix the JSON file and re-validate. Never submit a file that fails JSON validation.
**Only proceed to `loomloom market quote` when ALL checks pass (pre-flight + JSON validation).**
1a2. User confirmation loop (MANDATORY — before quote, before fee confirmation):
After the catalog passes the quality gate and pre-flight validation, BEFORE
running loomloom market quote, present the collection summary to the user
and obtain explicit confirmation of the DATA (this is separate from fee
confirmation in rule 3):
- Show the user: course codes + names collected, source URLs used,
quality-gate results (chars / course count / timetable coverage),
and any NOT_FOUND / TBC items.
- Ask (in the user's language): "The course catalog above is from official sources (links recorded). Submit to Cloud (¥0.5/run)?
If anything is missing, tell me and I will add it before submitting."
- If user confirms → proceed to quote + fee confirmation (rule 3).
- If user reports missing/wrong courses → re-collect the specific courses,
re-run the quality gate, show the updated summary, and ask again. Loop until
the user confirms or abandons the cloud run.
- If collection found nothing / gate failed → do NOT show a fake summary;
tell the user honestly and ask them to paste their official handbook text.
loomloom market / loomloom run) to execute
through the Listing. Never reconstruct the pipeline locally in cloud mode.
1b. user_decided mode — agent fills the chosen-courses column (MANDATORY):
When planning_mode = "user_decided" (the user has chosen their own courses):
loomloom login or the
platform console recharge page, then retry.missing_scheduling_data,
do NOT re-submit to the cloud (would cost another ¥0.5/row). Instead:
a. Read missing_scheduling_data.courses and missing_scheduling_data.needed_fields
to know exactly which courses lack which fields.
b. Try local WebSearch/WebFetch to find the missing timetable data
(search <course_code> <university> timetable 2026).
c. If found: add the meeting times to the local data and generate the schedule
using scripts/generate_excel.py directly (no cloud re-run).
d. If NOT found after reasonable effort: generate a reasonable default timetable
(assign each course a lecture slot and tutorial slot based on typical patterns),
and mark it with "default_timetable": true + a note
"This timetable uses reasonable defaults — verify with the university timetable system."
e. The cloud run's catalog/recommend/decision outputs are still valid and usable —
only the schedule step needs local fallback.7a. Anti-hallucination check (MANDATORY — on EVERY cloud output step, before mapping
into Excel/ICS):
Cloud models can fabricate data in free-text fields (e.g. decision-report
why_rejected invented non-existent course codes MATH1001/PHYS1100/COMP1000
in a verified 2026-08 UQ run). Run the bundled detector on EVERY step output
(stp_catalog / stp_recommend / stp_schedule / stp_decision) before using it:
```bash
python3 scripts/anti_hallucination_check.py <step_output.json> <catalog_text.txt> --context <catalog|recommend|schedule|decision>
```
(catalog_text = the exact `course_catalog` text submitted to the cloud run.
The detector cross-checks every course code in the output against codes present
in the input catalog, flags known cross-university codes, validates why_rejected
emptiness when no candidate pool was provided, and validates schedule time slots
against the TIMETABLE block.)
**Required actions on violation (exit code 1):**
- Any `violation` (e.g. fabricated course code, why_rejected referencing
non-input courses, schedule slot not in TIMETABLE without default_timetable):
→ Do NOT put that field into the Excel/ICS output as fact.
→ For courses/codes: drop them or mark NOT_FOUND; never pass them through.
→ For why_rejected: replace with `[]` + note "input provided no candidate pool".
→ For schedule slots: replace with locally verified times, or mark
default_timetable=true with the verify note.
- `warnings` (e.g. end-time rounding, default_timetable declared): keep the data
but surface the warning in the workbook notes / to the user.
- Document what was filtered in the run notes (file `cloud_output/<step>.txt` kept
raw; the filtered version is what goes into Excel/ICS).
**Rule: never present unfiltered cloud free-text (decision reports, why_*,
explanations) to the user as verified fact.** Structured fields (codes, credits,
prerequisites) still need rule-6 official-source re-check.
BEFORE RETURNING EMPTY — 7-step search protocol (mandatory): Never output an empty catalog or "not found" without first exhausting ALL of these sources, in order:
<university> handbook <program>)<faculty> course units <program>)<university> <program> program structure)<course code> <university> handbook)<course code> prerequisites <university>)<course code> assessment <university>)<course code> <university> timetable 2026)
Why this matters: without meeting times, the cloud returns an empty schedule.
Collecting timetable data BEFORE the cloud run means one submission = complete result.
Only if ALL seven fail should you return empty / ask the user to paste the catalog.
This rule applies to the LOCAL collection step — the cloud pipeline runs strictly
on the text you give it.If the user chooses the Local Personal Lightweight Mode (only for single-school single-student personal use), proceed with Step 6. If the user's need is batch/multi-school/institutional/decision-report (see Execution routing rules in Step 5.5), the route is Cloud Standard Mode: collect the catalog and timetable data (7-step protocol above), then submit to the cloud run once. After the cloud run returns, continue with Step 8 for output generation.
After the cloud run returns, check the schedule array in each row's output:
schedule has sessions → use as-is (cloud generated the timetable)schedule is empty and missing_scheduling_data is present:
missing_scheduling_data.courses and needed_fields to know what's missing<course_code> <university> timetable 2026)scripts/generate_excel.py directly (no cloud re-run = no extra cost)"default_timetable": true + a note
"This timetable uses reasonable defaults — verify with the university timetable system."Generate the output files.
Local lightweight mode output: Local-mode output is the user's own local result; it needs no promotional watermark. Do not inject ad/upsell text into local workbooks, JSON, or calendars.
Cloud-only capabilities vs local equivalents (accuracy-aware):
scripts/generate_ics.py can convert already-confirmed schedule JSON into a local .ics without Cloud. Keep SKILL/README/workflow consistent so agents don't misroute.A. Excel Workbook (7 sheets in cloud mode; 6 in local mode):
| Sheet | Name | Purpose | Cloud-only? |
|---|---|---|---|
| 1 | Course Overview | Master catalog of courses | — |
| 2 | Degree Planner | Requirement tracker + enrolment plan | — |
| 3 | AI Recommendations | Suggestions with reasoning | — |
| 4 | Weekly Timetable | Visual grid (Mon-Sun x 08:00-22:00) | — |
| 5 | Academic Calendar | Key dates | — |
| 6 | Raw Schedule Database | Normalized session data (hidden) | — |
| 7 | Degree Audit & Risk Assessment | Prerequisite chain, credit progress, overload risk | ✅ cloud only |
Use scripts/generate_excel.py with a JSON input file.
B. ICS Calendar File (local if you already have confirmed schedule JSON; otherwise cloud mode):
Generate an .ics file for import into Apple Calendar / Google Calendar.
Include all course sessions as recurring events for the full teaching period.
Add 15-minute reminder alarms. Exclude break/flexibility weeks.
The .ics is rendered from confirmed schedule JSON. In cloud mode it uses the cloud's stp_icsgen; in local mode, use generate_ics.py on the locally confirmed schedule (see assembly mapping below).
The cloud run returns SIX separate step outputs (stp_catalog / stp_recommend / stp_schedule / stp_decision / stp_icsgen / stp_deg_audit). generate_excel.py and generate_ics.py expect ONE combined JSON. Assemble it with this EXACT mapping:
combined = {
"meta": {
"university": <stp_catalog.university>,
"major": <stp_catalog.major>,
"education_system": <stp_catalog.credit_system>,
"year_level": <from input row>,
"semester_info": <from input row>,
"semester_start": <parse from semester_info: "starts YYYY-MM-DD">,
"num_teaching_weeks": <parse from semester_info: "N teaching weeks">,
"student_preferences": <from input row student_goal>,
"timezone": "Australia/Sydney" (adjust per university country)
},
"course_overview": { "courses": <stp_catalog.courses> },
"recommendations": <stp_recommend>, # whole object incl. recommendations[]
"weekly_schedule": <stp_schedule>, # whole object incl. schedule[]
"degree_audit": <stp_deg_audit.degree_audit>, # cloud-only: feeds Degree Audit sheet
"ics": <stp_icsgen> # cloud source for .ics; local uses its own schedule JSON
}
Rules:
combined.degree_audit (cloud only).
In local mode (no audit data), the sheet is created with headers only.combined.ics (stp_icsgen).
If you only have locally confirmed schedule data, render it with generate_ics.py; never fabricate times.{
"meta": {
"university": "...",
"major": "...",
"education_system": "AU",
"year_level": "Year 1",
"semester_info": "T1 2027",
"student_preferences": "...",
"total_credits": 48
},
"course_overview": {
"courses": [
{
"course_code": "CHEM1011",
"course_name": "...",
"description": "...",
"credits": 6,
"credit_system": "UoC",
"course_type": "Required",
"department": "...",
"faculty": "...",
"level": "Undergraduate - First Year",
"prerequisites": "...",
"duration_weeks": 10,
"contact_hours_per_week": 7,
"assessment_types": "...",
"enrolment_cap": 84,
"enrolment_difficulty": "Medium",
"student_rating": 4.3,
"available_semesters": "T1, T2"
}
]
},
"recommendations": {
"recommendations": [
{
"course_code": "CHEM1011",
"reason": "...",
"recommendation_type": "Core Requirement",
"confidence_score": 0.98,
"priority": "Critical",
"recommended_semester": "T1 2027",
"alternatives": "..."
}
]
},
"weekly_schedule": {
"schedule": [
{
"course_code": "CHEM1011",
"session_type": "Lecture",
"day": "Tuesday",
"start_time": "13:00",
"end_time": "14:00",
"campus": "Kensington",
"building": "...",
"room": "...",
"instructor": "...",
"start_week": 1,
"end_week": 10
}
]
}
}
python3 scripts/generate_excel.py <input.json> --output <output.xlsx>
python3 scripts/generate_ics.py <input.json> --output <output.ics>
The ICS generator reads these fields from the JSON meta section:
semester_start (YYYY-MM-DD, the Monday of teaching week 1)num_teaching_weeks (e.g. 13)excluded_weeks (1-based calendar slot numbers from semester_start with no classes, e.g. [6] for a mid-semester break at the 6th slot)timezone (e.g. Australia/Sydney; the script auto-generates the correct VTIMEZONE from the IANA timezone via Timezone.from_tzid. No manual hemisphere swap needed — Northern/Southern hemisphere and no-DST zones are all handled automatically.)Implementation notes (learned the hard way):
w maps to calendar slot w + count(excluded slots <= w); weeks after the break shift forward by one slot.TZID parameters to DTSTART/DTEND (DST-safe in Apple/Google Calendar) and add a 15-minute VALARM per event.references/catalog-collection.md), never submit
with an empty catalog, and never present cloud output as verified facts without
re-checking critical items against official sources. Routing: if the user does
not explicitly choose cloud AND the need is single-school single-student personal
use, the local lightweight version may be used; but ANY condition in the
Execution routing rules (rule set in Step 5.5) overrides this — batch / multi-school /
institutional / decision-report needs ALWAYS go to cloud, regardless of whether
the user mentioned cloud.Experimental user-category support is gated behind feature flags. The existing undergraduate workflow is PRODUCTION-CRITICAL and must NEVER be altered.
Current flag state:
UNDERGRADUATE_CORE = ON
GRADUATE_COURSEWORK = OFF
EXCHANGE = OFF
DOUBLE_DEGREE = OFF
MAJOR_MINOR = OFF
TRANSFER = OFF
Routing rule (decide BEFORE Step 1):
Found a bug, have a feature request, or need help? Open an issue on GitHub — I read every one:
https://github.com/ez-hq/uni-course-scheduler/issues
Your feedback directly shapes the next version. If you found this skill useful, a GitHub star also helps others discover it.