# Reading the other ANU sources directly **This is a fallback. If you can run `anu-pandc`, stop reading and run it.** Its companion, [reading-pandc-directly.md](reading-pandc-directly.md), covers Programs & Courses. This one covers the sources the CLI reads alongside it: the **Policy Library**, **University legislation**, the **class timetable**, the **exam timetable** and the **university calendar**. Same caveat as the companion — hand-fetching re-derives parsers that already exist, and the CLI is the right answer wherever it runs: ```bash uvx anu-pandc policy get ANUP_004603 ``` Everything below was checked against the live sites on 2026-09-20 (the exam timetable on 2026-09-28). ## What lives where A question about coursework usually needs more than one of these, and they answer different kinds of question. Getting the source wrong is the commonest way to give a confidently wrong answer. | Question | Source | |----------|--------| | What must happen? Who may decide it? | **Legislation** — Statutes, Rules, Orders. Binding, federal law. | | How does the University apply that? | **Policy Library** — policy, then procedure under it. | | What does this course require of a student? | **P&C** course page, then the **class summary** for the real assessment. | | When does it happen, university-wide? | **University calendar** — census, breaks, exam periods, results. | | When and where does a class meet? | **MyTimetable** — scheduled, not delivered. | | When and where is a course's exam? | **Exam timetable** — only while its exam event is open. | Legislation beats policy, policy beats procedure, and a class summary beats all of them for what a particular class actually did. If a policy and a Rule disagree, the Rule wins and the policy is out of date. ## 1. The ANU Policy Library Base: `https://policies.anu.edu.au`. Server-rendered HTML; no JavaScript needed, no year in any URL — a document has an effective date, and only the current version is published. Documents are numbered `ANUP_004603`. | What | URL | |------|-----| | One document | `/ppl/document/ANUP_004603` | | Every document, A–Z | `/ppl/title/index.htm` (≈495 rows, one page) | | Everything of one type | `/ppl/view_all/index.htm?subdoctype_id=Policy` | | Everything under a topic | `/ppl/view_all/index.htm?ssTopic=Students&ssSubTopic=Assessment` | | Keyword search | `/ppl/search_results/index.htm?searchtype=QUICK&ssUserFullText=delegated+authority` | `subdoctype_id` is one of `Policy`, `Procedure`, `Standard`, `Guideline`, `Form`. A document page has three parts worth reading: - `div#convertedcontent` — the document body, converted from Word. - `div#info_block` — a two-column table of the governance metadata: document type and number, **effective date**, **next review date**, **responsible officer**, **approved by**, **contact area**, and **Authority**, which links the legislation the document is made under. This is what tells you whether a document still binds anyone. - a Related Content box linking the policy to its procedures and forms. ### Gotchas - **Clause numbers are in the markup, not the text.** A policy cites its own clauses ("in accordance with Clause 73"), and a single run of clauses is split across dozens of `
/sections`, `subject//activity_groups`,
`locations///`.
### Gotchas
- **`activitiesDays` is the only thing that locates an activity in time.** A
row's day and time are a weekly *pattern*; the dates say which weeks it
actually runs. A search returns every teaching period of the year at once,
so a response with both semesters in it is normal and the period is not
implied by the date you are asking about. To answer "what is on in the week
of 21 September 2026", filter `activitiesDays` to Mon–Sun around that date
and report only what survives — do not read a weekly pattern and assume it
applies. Deciding between First and Second Semester by guessing where in the
year a date falls is how this goes wrong; the dates are right there.
- **Count sessions from `activitiesDays`**, not by decoding `week_pattern`.
- **Drop `activity_type: "Clone"`.** Those duplicate a real slot, carry no
location, and double every count made over them.
- **Several streams of one group are alternatives.** ComA/01–04 are four
offerings of the same lab; a student attends one. Counting all four turns 5
contact hours into 20.
- `staff` is almost always `-`. The timetable does not say who teaches. The
convener is on the class summary in P&C.
- Co-taught codes are in `description`
(`COMP3300_S2_(01)-ComA/01 + COMP6330_S2_(01)-ComA/01`). A code is followed
by an underscore, which is a word character, so `\bCOMP\d{4}\b` never
matches. Use a lookahead.
- This is the **scheduled** timetable. It changes, and it is not evidence that
anything was delivered. Required contact hours come from P&C and the class
summaries; check the dates against the calendar's teaching breaks and public
holidays.
## 4. The exam timetable
`https://exams.anu.edu.au/timetable/login.php` — server-rendered PHP, no
JavaScript needed. The Examinations Office publishes each exam *event*
("Semester 2 - End of Semester, 2026", "Semester 2 - In-Class & Online, 2026")
as its own database, chosen with `db`.
| What | How |
|------|-----|
| Events open right now | `GET login.php?db=0`: a link per event, `login.php?db=N` |
| One event's search form | `GET login.php?db=N` |
| Search | `POST login.php?db=N` with form field `Code=COMP1100,COMP2300` |
| Next page of results | `GET login.php?skip=20`, then `skip=40`, … on the same session |
```bash
curl -s -c jar -b jar 'https://exams.anu.edu.au/timetable/login.php?db=13' > /dev/null
curl -s -c jar -b jar --data 'Code=COMP1100' 'https://exams.anu.edu.au/timetable/login.php?db=13'
```
Results are `table#table-compact`, one row per exam *per room*, with a header
row naming the columns. The end-of-semester event has Exam Code, Exam Title,
Assessment Type, Date (`Friday 20/11/2026`), Time (`2:00pm`), Writing Time and
Reading Time (minutes), Venue, Building (the ANU building number) and Room.
"Sorry, no exams were found." means just that.
### Gotchas
- **Most of the year there is nothing here.** An event appears only when the
Examinations Office releases it and closes on a fixed date; after that its
page reads "Sorry, this feature is not available after DD/MM/YYYY". An empty
or irrelevant index is a normal answer — say the exam timetable is not
published yet, and give the calendar's exam period instead. Do not infer an
exam date from anything else.
- **`db` numbers are recycled slots, not ids.** `db=14` held Semester 1 2024,
`db=1` Semester 1 2025, `db=13` Semester 2 2026. Always start from `db=0`;
never guess or bookmark a number.
- **Columns differ between events.** The in-class event has no Reading Time or
Room. Read the header row; never index cells by position.
- **The session carries state.** The pager links (`?skip=20`) name neither the
event nor the search — the PHP session cookie does. And once a session has
searched one event, a POST straight to another answers HTTP 500 until that
event's page has been loaded with a GET. Load the event page, then search.
- **One exam, many rows.** A large exam is published once per room (COMP1730:
seven rooms). Group by exam code, date and time before counting exams. A
student sits in one room, and the table does not say which.
- **Co-taught courses share an exam code**: `COMP1110/COMP1140/COMP6710_Semester 2`.
What follows the first underscore is the sitting, and may carry stray
numbers (`COMP3300_Semester 2 / 3300`) or a second sitting
(`MATH1014_Semester 2-1`).
- **Search is a prefix match on each part of the code**, case-insensitive:
`COMP6710` finds the combined code above, `COMP` finds every COMP exam, and
`6710` finds nothing.
- The form carries a hidden `_token`. The server accepts a search without it
today; send it back if you have loaded the form anyway.
- The page status beside the event name ("Final Timetable") is the release
state. Quote it along with the date.
## 5. The university calendar
Published twice, as a page and as a feed:
| Purpose | URL |
|---------|-----|
| The page, and the thing to cite | `https://www.anu.edu.au/directories/university-calendar?year=YYYY` |
| Machine-readable | `https://www.anu.edu.au/directories/university-calendar/YYYY/calendar.ics` |
The feed works for 2026 (55 events) and 2027 (62), and is served with
`cache-control: max-age=900`, so it is near-live. Each `VEVENT` has `DTSTART`,
`SUMMARY` and `URL`.
### Gotchas
- **Every event is a single day.** There is no `DTEND`; a range is published as
two events, "… begins" and "… ends". Pair them yourself.
- **"Return from X" is the first day *back*, not the last day off.** The two
closing shapes do not mean the same thing: "Semester 1 ends" is inclusive —
29 May is the last day of semester — but "Return from teaching break" on
21 September 2026 is the Monday teaching *resumes*. Ending the break on it
puts a teaching break over a full teaching day. The 2026 Semester 2 break is
7–20 September; the week of the 21st is week 7, with lectures, labs and an
assessed reflection task. Subtract a day from a "Return from" date before
treating it as an end.
- **If the timetable and your paired calendar range disagree, you have paired
it wrongly.** The timetable is generated from the teaching schedule and
knows about breaks. Scheduled classes sitting inside what you computed as a
break is the symptom of the off-by-one above, not a conflict to resolve in
the calendar's favour. Never advise anyone to skip a scheduled class on the
strength of a range you paired yourself.
- **The wording of the pairs drifts.** A break "commences" and you "Return
from" it — the marker is at the *front* of the closing summary. The
Semester 1 examination period has no "begins" at all, only an "ends". A
deferred period is "… period 1 begins (two week duration)", with the marker
before a parenthetical. And one pair in 2026 opens with a hyphen and closes
with an en dash. Match on keywords, and normalise before comparing.
- **`DTSTART` is `T000000Z`** although the date meant is the Canberra local
one. Read the date part only. Converting the timezone moves Semester 1
census day from 31 March to the 30th.
- HTML entities appear in `SUMMARY` (`'`); unescape them.
- The `audience` filter is unreliable — `?audience=` on the feed returns all
events regardless. Filter yourself.
- Page metadata reports `modified_time` 2024-10-14 even for 2027 content, so it
tells you nothing about freshness. Cross-check any date you will rely on
against the HTML page, and cite that page.
- These are university-wide dates. Course assessment dates are in the class
summaries; school deadlines (curriculum rounds, class summary due dates) are
published somewhere else entirely.
## Network
These are the hosts involved, for an egress allow-list:
```
programsandcourses.anu.edu.au programs, courses, class summaries
policies.anu.edu.au the Policy Library
mytimetable.anu.edu.au the class timetable
exams.anu.edu.au the exam timetable
www.anu.edu.au the university calendar, the legislation index
api.prod.legislation.gov.au Register metadata
www.legislation.gov.au Register document text
```
A 403 carrying an `x-deny-reason` header is your own gateway, not ANU, and no
header or proxy change gets around it. `anu-pandc` says which of the two
refused you.