Appearance
Education contract
The Education tab reads one table, programme. Like the job feed, Agisse never writes it: a separate feed provider does. A self-hosted copy starts with an empty tab.
The column list is checked against the Drizzle schema (worker/db/schema.ts) by worker/db/feed-contract.check.ts, so it can't drift.
programme
Upsert on url. Rows are public facts about programmes: no personal data.
| Column | Type | Required | Meaning |
|---|---|---|---|
url | text | yes | The official programme or position page; the primary key |
source | text | yes | edu:<scraper>, for example edu:uni or edu:euraxess |
kind | text | yes | master, mba, phd (a programme) or phd_position (funded, with a deadline) |
title | text | yes | As the institution names it ("MSc Financial Economics"); HTML is stripped before display |
degree | text | no | MSc, MA, MBA, MiM, LLM, MPhil, PhD, ... |
institution | text | yes | "University of Oxford" |
school | text | no | Faculty or business school, when it differs from the institution |
domain | text | no | The institution's website (ox.ac.uk), for its logo |
country | text | no | ISO 3166-1 alpha-2, upper case (NL, US); programmes are worldwide |
city | text | no | City |
fields | text[] | no | Keys of VACANCY_FUNCTIONS (see the feed contract) where they fit, else lower-case field names (economics, computer science) |
languages | text[] | no | Teaching languages, lower-case English names (english) |
duration_months | integer | no | Length of the programme |
tuition_eur | integer | no | Per year, in €, for international (non-EU) students; null when unknown. The tuition filter leaves unknown fees out |
study_mode | text | no | full_time, part_time or online |
start_date | date | no | Next intake |
deadline | date | no | Next application deadline; null when unknown |
deadline_type | text | no | explicit, rolling or unknown (default), as on vacancy |
description | text | no | Short plain text, at most about 2000 characters; feeds full-text search |
rank_the | integer | no | The institution's position in the THE World University Rankings |
rank_qs | integer | no | Its position in the QS World University Rankings |
rank_ft | integer | no | Its position in an FT ranking (business schools) |
first_seen | date | yes | First day the provider saw it |
last_seen | date | yes | Last day the provider saw it listed; refresh it on every run |
closed_at | date | no | Set once the provider has not seen it for 60 days |
search | tsvector | no | Generated by Postgres from title, institution and description. Don't write it |
details | jsonb | no | Anything else: GMAT, scholarships, funding, supervisor; defaults to {} |
When a programme is open
The tab shows a programme while closed_at is null and deadline is null or today or later. A seeker's saved programmes stay in their Saved list after they close, marked closed.
Rows are archived, never pruned: a closed programme keeps its row so past cycles' dates stay on record. Only a takedown request deletes one, and that also removes it from every seeker's Saved list.
Ranking
No model call. For each seeker: their degree's field words against the programme's text (half the score), a field they picked, the institution's best ranking position, whether it is the next step after their degree (a master's after a bachelor's, a PhD after a master's), and a deadline within a month (searchProgrammes in worker/lib/programmes.ts).