Skip to content

refactor: let the model own catalogue extraction - #133

Merged
HarryRandall merged 6 commits into
mainfrom
refactor/model-owned-extraction
Sep 23, 2026
Merged

HarryRandall merged 6 commits into
mainfrom
refactor/model-owned-extraction

Conversation

@HarryRandall

Copy link
Copy Markdown
Owner

Summary

First of a stacked series that hands catalogue syncs to the model entirely.

Why prerequisites were stored as free text. FINM3006's rule ("FINM2001; FINM2002; and, FINM2003 or FINM3011") was lost twice: the deterministic grammar could not read ANU's semicolon lists, and the model's correct rule was then discarded because its evidence was filed under requisites rather than requisites.prerequisiteRule. The projection fell back to one free-text condition, which the graph drew as a single box.

This PR removes the deterministic side instead of patching it:

  • One faithful page converter. Both kinds now send the whole page to the model as Markdown. The structure converter used to drop the key facts box, which is why college, length and selection rank were always empty.
  • The model owns every field. Both deterministic extractors and both merges are gone, including the structure merge that kept title, sections and outcomes for itself. Whatever fits the contract is stored. A field that does not is left empty with an error flag, one bad array item costs only that item, a malformed requirement branch becomes labelled free text instead of costing the tree, and wording the page lacks is kept with a warning.
  • Prompts ask for tidied display text (capitalisation, British spelling, typos, Markdown, page furniture such as "Back to the top") with the page's exact wording kept as evidence, and they spell out ANU's semicolon lists.
  • No prose fallbacks at runtime. The course page no longer parses prerequisite wording, the graph no longer recovers edges from old snapshots or draws bare codes from wording, the projection no longer scrapes codes into references, and the unused automatic mapping is removed.
  • Syncs still run only from a record's Sync button; nothing calls OpenRouter on its own.

Live check against the real pages with google/gemini-3.1-flash-lite:

Page Before After
FINM3006 one free-text condition all_of[FINM2001, FINM2002, one_of[FINM2003, FINM3011]], no flags, $0.004
AACOM 9 of 13 conditions free text; college, length, rank empty 4 groups, 17 typed conditions, no free text; college, 4 years, rank 85; $0.015
ADMA-SPEC "Back to the top" in the advice co-requisite majors as relationships, clean advice, $0.007

Verification

  • pnpm verify (format, lint, types, 520 unit and component tests, production build, 8 access tests, whitespace)
  • Browser smoke test: not run; the visible change is the graph's empty-state wording
  • Supabase migration: not run locally. Docker Desktop was not running and the local stack is shared with other tasks, so this relies on the PR database gate.

Deployment notes

  • Migration 009_retire_deterministic_extraction.sql deletes the audit rows of the retired deterministic_extract stage and deterministic_output artefacts, then drops both values from their check constraints. It is safe on existing data. The matching storage objects are left in the course-import-artifacts bucket.
  • Parser and prompt versions are bumped for both kinds, so any sync still queued from the old worker fails with "start a new sync".
  • Stored provenance can still say deterministic until the catalogue content is cleared; that is handled separately.

The course and structure converters each picked sections to keep, and the
structure one dropped the key facts box, so college, length and selection
rank never reached the model. One converter now keeps every visible part of
the page in order and removes only page furniture.
Course and structure syncs no longer run a deterministic extractor or merge
its output with the model's. The model reads the whole page and owns every
field, including the structure title, sections and outcomes the old merge
kept for itself. Whatever part of the response fits the contract is stored;
a field that does not is left empty with an error for review, one bad array
item costs only that item, and wording the page lacks is kept with a warning
rather than rejected. The prompts now ask for tidied display text with the
page's exact wording kept as evidence.
The stage and its artefact kind no longer exist in the pipeline. Their audit
rows are removed and both are dropped from the allowed values.
Only the model's rule says what a course requires. The course page no longer
parses the prerequisite wording, the course graph no longer recovers edges
from older snapshots or draws bare codes found in the wording, the
projection no longer scrapes codes from prose into rule references, and the
unused automatic mapping in the requisite editor is gone. A draft preview
now collects prerequisite codes the way the database does, and a course whose
wording has not become a rule says so instead of claiming it has none.
A requirement tree lost every condition when one nested group broke the
contract. A stray minimum count on a group whose operator does not use one is
now cleared, and any node that still does not fit becomes a free-text
condition holding its own wording, flagged for review, so the rest of the tree
survives.
@vercel

vercel Bot commented Sep 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
coursemap Ready Ready Preview Sep 23, 2026 7:09am UTC

@HarryRandall
HarryRandall merged commit d950521 into main Sep 23, 2026
6 checks passed
@HarryRandall
HarryRandall deleted the refactor/model-owned-extraction branch September 23, 2026 07:10

This branch was successfully deployed

1 active deployment
Preview 4e89f037 Deployed Sep 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant