Extracting Attendee Lists From Meeting Minutes
9 min read · updated August 11, 2026
The attendance block at the top of a set of minutes looks like the easiest extraction in the document. It is a list of names under a heading. The reason it is not easy is that the headings carry meaning that the names do not, and the usual output — an array of strings called attendees — throws all of it away.
The states an attendance block encodes
A formal set of minutes distinguishes at least four states, and often five. Somebody was present. Somebody was present in a non-member capacity. Somebody sent apologies, meaning they gave notice and the absence is excused and recorded as such. Somebody was absent without notice, which most templates render simply as an absence or omit entirely. And somebody attended for part of the meeting only.
These are not shades of the same thing. Attendance in a governance document exists mainly to evidence quorum — whether the meeting was competent to decide anything — and to evidence that members were given the opportunity to attend. Apologies are the record that notice was given. An absence with no apology is a different fact about the same person, and in some organisations a run of them has consequences under the constitution. Collapsing both into “not in the attendees array” means the two are now indistinguishable, and you cannot recover the difference later because the distinction only ever existed in the heading you discarded.
The vocabulary varies and you should pin it rather than guess. British and Commonwealth templates use “Apologies” or “Apologies for absence”. North American ones tend toward “Regrets” or “Excused”. Some use “Absent (excused)” and “Absent (unexcused)” explicitly. All of them mean roughly the same thing and none of them is a synonym for “absent” on its own.
Present and in attendance are not synonyms
This is the distinction that most often surprises people writing the extractor, and it is the one with the most consequence. In the conventional layout of company and charity board minutes, Present lists the members of the body — the directors, the trustees, the councillors — who are entitled to vote and who count toward quorum. In attendance lists everybody else in the room: the company secretary, the finance director who is not a board member, the auditor, a consultant presenting one item, a minute-taker.
People in attendance do not count toward quorum and do not vote. So an extraction that merges the two blocks into one list produces a number that looks like an attendance count and is not one. If anything downstream compares that count against a quorum threshold, it will find a quorate meeting whenever enough guests turned up. That is a quiet, plausible, entirely wrong answer, which is the worst kind.
Keep the heading each name appeared under, verbatim, alongside your normalised state. The normalised state is what you query on; the verbatim heading is what you show a human when they ask why a person was classified the way they were, and it is what lets you fix a mapping later without re-running the model over the whole archive.
People who were there for part of it
Partial attendance is recorded in the body of the minutes rather than in the block at the top, which is why extractors miss it. The conventional wording is a line inside an agenda item: “Ms A joined the meeting at 10:15”, “the Head of Risk left after item 6”, “Mr B withdrew for item 8 having declared an interest”.
The last of those is the one that matters most. A withdrawal for a declared conflict of interest is a deliberate, minuted act, and it means that person was specifically not present for the decision recorded under that item. If you are joining attendance to decisions — a reasonable thing to want — a flat attendee list will tell you they voted on something they recused themselves from.
You do not need to solve this perfectly to be useful. Extract the arrival and departure events as their own records, each with the person, the direction, and the item number or clock time as stated, and leave the interval arithmetic to a query. Attempting to compute a per-item attendance matrix inside the model is where hallucination starts, because the model has to invent the items nobody mentioned.
Modelling it
{
"attendance": [
{
"name_text": "J. Okonkwo",
"heading_text": "In attendance",
"state": "in_attendance",
"role_text": "Company Secretary",
"is_member": false
},
{
"name_text": "R. Patel",
"heading_text": "Apologies for absence",
"state": "excused_absent",
"role_text": null,
"is_member": true
}
],
"presence_events": [
{ "name_text": "R. Alvarez", "direction": "joined",
"at_item": "4", "at_time": "10:15", "reason_text": null },
{ "name_text": "T. Boateng", "direction": "left",
"at_item": "8", "at_time": null,
"reason_text": "declared an interest" }
]
}The state enum should be closed and small: present, in_attendance, excused_absent, absent, unknown. Push everything you are not sure about into unknown rather than growing the enum per template, and let heading_text carry the variation. is_member is separate from state because a member can be absent and a non-member can be present, and conflating them is the same mistake as merging the blocks.
Names are the other half of the problem. Attendance blocks use initials, honorifics, post-nominals and role suffixes inconsistently within a single document — the same person can be “Dr S. Meyer (Chair)” at the top and “SM” in the action table. Store name_text exactly as written and do identity resolution as a separate, reviewable step. It is also the step where personal data handling starts to matter, because an attendance record is personal data about a named individual whether or not anything else in the document is.
Where the extraction goes wrong
- Names run together across a line break. Attendance blocks are often set in two or three columns, and a PDF text layer reads across the columns rather than down them. You get “A. Smith B. Jones” as one name. This is a reading-order problem before it is a model problem, and it is fixed upstream.
- The chair is named twice. Once in the attendance block and once in a “Chair: ” line above it. Dedupe on normalised name, keep the role from whichever mention carries one.
- A delegate is recorded in place of a member. “Mr C (alternate for Ms D)” is one person present and one person absent, and the parenthetical is the only evidence of the second. If your schema has nowhere to put it, it disappears.
- Apologies are noted in the body, not the block. “Apologies were received from Ms E” sometimes appears under item 1 instead of the header. If you only read the block, that person is silently absent-without-notice.
- Quorum is stated and you did not capture it. Many templates include an explicit line — “a quorum being present, the Chair opened the meeting”. That sentence is the document asserting the answer, and it is worth extracting as its own boolean, because it lets you check your own count instead of trusting it.
Attendance is also the namespace the rest of the document resolves against. Once you have it, the initials in the action table stop being ambiguous, which is why it is worth doing before extracting the actions and their owners.