■ REFERENCE DOCUMENT NO. 002

the log

● PUBLIC ARCHIVE DEPOK, ID
2.0LOG ENTRIES

The Pacil Matkul Tree

Five years ago, some less-than-occupied students from the Faculty of Computer Science at the University of Indonesia were thinking about how hard it is to plan semesters, especially as freshmen. For posterity, this is what the academic website looked like back then:

feast your eyes upon SIstem AKademik Next-Generation (SIAK-NG)

The story concerning SIAK-NG shall come at another time. Anyways,
if you were to plan courses, you had two options:

  1. Read the entire academic handbook (>280 pages!)
  2. Read the "Jejaring Mata Kuliah"

Jejaring Mata Kuliah how yours truly planned his courses. yes, that is figjam

So, what is the problem?

  1. Reading 280 pages is just... no
  2. Reading only Jejaring Mata Kuliah won't let you easily see the non-mandatory classes

This was back when LLM-based AI was nowhere "there" yet. So, having just graduated from the web programming and design course, the solution was, of course, ein web app! initial commits
Initially committed in June 2021

ChatGPT

ChatGPT's release is... a year away from the initial commit

So, what was made? legacy app hehe material ui goes brr

The code, however, was horrible. Being a dumb kid who had never heard of TypeScript before, guess where the data, including the selection state, was stored? Yep! In a JSON file! LMAO!

Components? What components? Everything is inline :D

The data was the worst part—I was not manually tagging data for 150 courses into JSON! Well, actually, I kinda did. Just a subset, though: only the mandatory courses, so we get:

Just the mandatory courses! Literally no improvement over the FigJam approach!

So, what was the solution? Well, a man smarter than me, Mustafa, tried to build one with Go: https://github.com/mustafasegf/extract-kurikulum Sadly, I gave up on learning Go at the time. So, of course, I tried running it with ChatGPT a few months later. The issue was that ChatGPT 3 was... nowhere near up to the task.

Over the next few semesters, I tried recruiting some underclassmen to help label the data, but that arrangement fell through. Oh well, no option but to abandon it.

But now, with the power of frontier LLM models, I was able to run an extraction job over the two new FASILKOM handbooks. I am sure this is a non-negligible amount of water that evaporated from the California water table

But you may be asking: how? Running AI is not cheap, and LLMs famously drift with context. Several things:

First, we build a stupid-simple, stupid-robust typing system. For that, we use good old Zod, a schema validation library.

export const CourseEditionSchema = z
  .object({
    id: EntityIdSchema,
    curriculumId: EntityIdSchema,
    courseId: CourseIdSchema,
    title: LocalizedTextSchema,
    syllabusCredits: z.number().positive(),
    description: LocalizedTextSchema.optional(),
    topics: z.array(TextBlockSchema).optional(),
    syllabusPrerequisites: RequirementDeclarationSchema,
    corequisites: RequirementDeclarationSchema.optional(),
    programOutcomeMapping: ProgramOutcomeMappingSchema.optional(),
    assessment: z
      .object({
        rawText: NonEmptyStringSchema,
        methods: z.array(NonEmptyStringSchema).min(1),
      })
      .strict()
      .optional(),
    practical: PracticalSchema.optional(),
    learningOutcomes: z
      .array(
        z
          .object({
            id: NonEmptyStringSchema.optional(),
            text: LocalizedTextSchema,
          })
          .strict(),
      )
      .optional(),
    learningResources: z
      .array(
        z
          .object({
            citation: NonEmptyStringSchema,
            url: z.string().url().optional(),
          })
          .strict(),
      )
      .optional(),
    sourceRefs: z.array(SourceReferenceSchema).min(1),
    reviewStatus: z.enum(['needs_review', 'reviewed']),
    issues: z.array(ExtractionIssueSchema).optional(),
  })
  .strict()

This is made to match how the faculty publishes course info in the handbook:

Now, how do we communicate this to the model? What harness did I use? Surprisingly, web ChatGPT! Well, I cheated. After working with that organization on that project, running models too big for a poor, poor A10, there was no way in hell a self-hosted Gemma4 or Qwen3.6 would successfully parse this (without making me have to go shopping for a new GPU). This run used OpenAI's ChatGPT 5.6 Sol PRO (that's why it ran for 81 minutes!) Prompt follows:

You are extracting authoritative curriculum data from university faculty handbooks. Your final output must be one JSON document matching the data contract below. Output JSON only: no Markdown fences, commentary, citations outside the JSON, or invented values. GOAL Extract: 1. Course catalog facts. 2. Prerequisite and corequisite rules. 3. Recommended semester placements. 4. Required/elective classifications. 5. Institutional classifications such as MKWU, MKWF, or MKWPS. 6. Course-to-program-learning-outcome mappings such as CPL IK 1–6. 7. Source provenance for every extracted record. CORE RULES - The official course code is the only course ID. - Never use an abbreviation or course title as an ID. - Preserve Indonesian source text faithfully. Normalize whitespace, but do not summarize or rewrite it. - Only add English text when the handbook supplies an English version. - Page numbers are 1-based PDF page numbers. - Never invent missing data. - Keep curriculum placement separate from the course catalog. A recommended semester, curriculum classification, or CPL mapping belongs to a particular curriculum, not permanently to the course. - Do not include UI state such as selected, available, expanded, or dialog-open. - Mark all machine-extracted courses as "needs_review". - Set the overall dataset status to "draft". - If agents disagree, preserve the source wording and add an issue instead of guessing.

How do I validate this? Well, with what people would call "using adversarial models to validate output," also known as tossing Kimi K2 (OpenCode), ChatGPT (Codex), and Claude (Claude Code) at it, of course! All three eventually gave up and began running JSON validators. ChatGPT tried to build its own for some reason. Claude, at some point, began trying to hand-fix the schema. Which is fine, except I accidentally used Fable 5. The entire week's quota was gone with the wind. Oh well, limits reset tomorrow.

In the end, the result was an initial report of 200 issues, validated through OpenAI's ChatGPT 5.6 Sol (High) running Deep Research.

Which, in turn, I punted back at GPT Pro, and it was able to make, um, educated guesses about what the resolutions should be. The failure modes were mainly prerequisites because similar-sounding courses exist between the Ilmu Komputer and Sistem Informasi curricula, i.e., Jaringan Komputer (JARKOM) vs. Jaringan Komunikasi & Data (JARKOMDAT), which are not the same. Well, if you ask the TAs, it's same same, but different, but still the same (seriously, though, it's the same course packaged differently, so in the eyes of the faculty, they are distinct).

Of course, the dataset requires human validation (me non-exhaustively testing the web app a bit).

Results? much better

So, yeah, this website is kinda made with AI. Not entirely, no. But the entire dataset was made by robots. The bright side? I was able to actually re-engineer the frontend lol. AI does not steal my job (for now), just the parts I hate doing. Hopefully, saying this won't bite me in the ass in five years.

Feel free to play with it at https://matkul.franciswibisono.com

Hello world!

At the insistence of career advisors (and an invented psychologist), I have finally stopped putting off making this... blog thing. More, as usual, to come.