Features

Preventing fraud is half the job. The other half is running the term.

An attendance system is more than a verification chain: a course is opened, a roster is uploaded, a class is cancelled, a make-up class is scheduled, a teacher falls ill, a student asks “how many absences do I have left?”. This page describes that side of the product.

Every line states what the work does and, just as plainly, its cost and its limit. Saving for the last meeting what an institution will find out in the first one helps nobody.

Measurement

We publish no figures

This page says what the product does. It gives no number for how well it does it — because producing that number means measuring in the field.

We publish no accuracy figures. The success rate of the face verification engine, the robustness of liveness detection and the duration of a round have not been measured. No number you see on this page is a performance claim; and where no number is given, we do not guess either.

Teacher

During class and after class

The work a teacher actually has to do over a term. For the usage steps, see the teacher page.

Live roster

While the class is running, who has checked in and who has not is visible on screen. This is not a convenience but a matter of timing: finding out after the session closes means finding out at the moment correction has become impossible.

Projecting the QR code

A QR code on a phone screen is readable from 1–2 metres. In a 200-seat lecture hall, “everyone come up and scan” is not a workable instruction; the QR code has to go on the projector. For that, the QR code can be handed over to the teacher's own laptop: the anchor screen is opened on the computer, the hand-off QR code on the phone is shown to the computer's camera, and from then on the QR code is generated and projected on that computer.

The hand-off does not go over the network: between the phone and the computer there is only a camera, and nothing goes to the server. Because the QR code is generated deterministically from time, no internet is needed either once the page is open — but the page itself is downloaded over the network, so it has to be opened before class begins. If the computer has no camera, the hand-off line can be pasted by hand; that route is riskier and the screen says so.

The cost is not hidden: with the hand-off, the session's anchor key moves to a second device, and anyone who sees the hand-off QR code obtains that key — which is why the code is shown briefly, not indefinitely. The system cannot verify which computer the page was opened on; “the teacher's computer is secure” is an assumption. How far the QR code can be read from when projected has not been measured.

Manual marking

A student whose phone is broken, whose battery is dead, who does not appear on the roster or whose verification fails is marked by hand. A reason is mandatory and is written to the audit log.

Without this screen the honest teacher's only remedy would be the paper route, and the paper route lowers the record to a lower assurance level and produces an audit signal.

Past sessions and export

The teacher sees past sessions of their own course in the app and exports the result of a session as CSV for upload to the student information system. The file is produced in a format Excel opens correctly.

Export in the teacher app is per session. A cumulative term-wide extract is available only from the admin dashboard.

Minimum stay threshold

“Check in and leave” is closed not only by extra rounds but also by duration: the teacher sets, per course, the minimum number of minutes a student must have been verified for in order to count as present.

Cases where the threshold cannot be reached are marked separately — was the time insufficient, or were the rounds too sparse for the threshold to be measurable at all? These are not the same thing and must not produce the same outcome for the student.

Classroom and time changes

A class can be moved to another room or another time. The change is more than a note: reminders shift accordingly and the “impossible travel” signal looks at the correct room.

Class cancellation and make-up classes

Cancellation and make-up classes are not special cases of moving a class but separate concepts: treating a cancelled class as a moved one would record absences for students from a class that never happened. The server distinguishes all three and applies a cancellation only to that date.

The screen in the teacher app enters all three, and each kind asks only for its own fields: a cancellation never asks for a room or a time, a make-up class requires a time. This is not a layout preference — letting a cancellation carry a room would quietly turn it back into a move.

Offline queue

Attendance taken while there is no network accumulates on the teacher's device and is sent when the connection returns. The state of the queue is visible on screen; the question “did it go through or not?” is not left ambiguous.

When the outcome of a submission stays uncertain, the queue marks it uncertain and does not silently retry. Writing the same record twice is prevented today by a database constraint; the server has no separate replay protection for this.

Digitising paper attendance

If the system could not be used at all, a photograph of the paper sheet is taken and attached to the record. These records are marked with the lowest assurance level and their share is reported: falling back to paper is not an escape hatch but a monitored exception.

Headcount in the room

If the teacher wants, the camera measures how many people are in the room and compares that with the number of verifications. Nobody is recognised, no embedding is produced, no frame is written anywhere; the output is a single number and is attributed to no student.

The number is a lower bound: students who overlap, sit at the back or fall outside the frame are not counted. That is why it is shown as “at least N people seen”. We do not know how many it counts in a crowded, dimly lit real classroom — that measurement has not been made.

For this reason the feature never runs by itself: the teacher has to start it explicitly. The count stays entirely on the device — the server has no concept of a room headcount — and it changes no attendance record.

The count can also fail, and that case is not left silent: if the camera does not open or frames cannot be read one after another, the screen says what happened instead of reporting “0 people”, and offers both a retry and a “I counted it myself” entry. A manually entered number is not a measurement and is not presented as one: it is labelled on screen as a teacher statement, does not use the measurement language of “at least N people seen”, is not compared with the verification count, is stored nowhere, and disappears when the screen is closed.

Substitute teacher

So that someone else can open the class of a teacher who has fallen ill, a time-limited and non-transferable delegation can be granted. The course's actual teacher does not change: “who teaches this course” and “who may act in this course” are kept separate, otherwise the substitute's markings would count against the actual teacher's transparency ratio.

Delegation is granted and revoked from the Section management screen on the dashboard; a reason is required, and revoking does not delete the row — who was authorised and when stays visible. The substitute has no separate screen in the teacher app: they open the class normally and the server recognises the permission.

Teachers seeing and deciding appeals

An appeal lands in front of the teacher of the course. The teacher app has an appeal queue opened from the “My courses” screen; its scope comes from the server and the teacher is limited to their own sections. An appeal is reviewed and then accepted or rejected with a written reason; a decision without a reason is blocked before the request is even sent.

Two things the screen deliberately does not do also need stating. The risk score or band is not shown: a decision made after seeing the score would no longer be an independent observation. The decision is not put in the offline queue: the decision is the answer given to the student, and an answer that arrives hours later is an answer not given. If submission fails, the appeal stays open, the reason you wrote remains in the form, and nowhere does it say “resolved”. The decision cannot be undone, and that is said before the decision.

Applying an accepted appeal to the attendance record happens on the same screen: once the appeal is accepted, the teacher makes the correction from there, writes a reason, and the correction goes into the audit log. The correction is deliberately a separate step — accepting an appeal and changing a record are not the same thing, and each is logged separately.

Student

What the student holds

The reason a student opens the app is usually a single question: how many absences do I have left? For detail, see the student page.

Receipts

For every successful attendance a signed receipt is created on the phone. Receipts are chained to one another; if one is silently removed from the middle, the chain breaks and that is visible.

With a single tap the student compares their receipts with the server record: if there is a discrepancy, they learn about it before filing an appeal. The comparison is started by the student; it does not run by itself in the background.

The signature of every receipt is verified on the phone and the result appears in the receipt list — details below, in the Evidence section.

Filing an appeal

For the “I have a receipt but there is no record” case, an appeal is filed from the app with a single tap and the receipt is attached to it as evidence.

The screen keeps its promise all the way. An appeal is filed for every receipt found missing — not just the first; loss usually happens across a session or a day, not one by one. The request is awaited: nothing is shown on screen until submission finishes. The outcome is stated, and three cases are kept apart: all filed — “recorded”; none filed — “could not be sent”; some not filed — that is said separately, with how many were recorded and how many never reached the server, receipt by receipt. Saying “done” silently was the worst option: the student would never file again for the attendance they lost. A failed appeal is not queued; the student is expected to retry, and the screen says so.

Seeing the outcome of an appeal

An appeal is decided, its reason is written, and an accepted appeal really does correct the attendance record — with the correction written to the audit log.

The student can check “what happened to my appeal?” from the app: the state of the appeal and the reason the teacher wrote are both visible. Showing the reason matters — without it the appeal would be a one-way box.

Attendance status and the absence limit

How many of the closed sessions were recorded as PRESENT / PARTIAL / ABSENT, the absence ratio, the per-term limit and the remaining allowance are computed on the server. How much weight partial attendance carries towards absence also comes from the institution's rule.

The student sees this in the app's My attendance tab: the ratio per course, the per-term limit and the remaining allowance. If no limit is defined, no number is invented, it says “not defined”; if the limit has been exceeded, that is stated plainly. The server's own caveat is shown verbatim on screen: this screen is informational and does not decide whether a course is passed.

Class reminders

Reminders can be received for upcoming classes, and they arrive by two separate routes. The first stays on the device: the phone fetches the weekly schedule and schedules the reminder itself. The second is a server notification — when a class is moved, cancelled or a make-up class is added, the server sends word. It also sends word when a change is undone, and says which: undoing a cancellation means “come to class”, undoing a make-up class means “do not”. A single “the change was undone” sentence could have sent the student to exactly the wrong place.

We are writing down plainly that we took back a promise. This page used to say “notifications are never sent by the server, no store of device tokens is ever created”. The moment server notifications were added that sentence became false: device tokens are now stored, and that is a privacy property we lost. The reasoning and the accepted risks are recorded in ADR-021.

A notification never carries: an attendance result, an appeal status, a risk flag, or the student's name or number. Only information about the class itself — course code, new time, new room. The reason is simple: a notification shows on the lock screen and anyone holding the phone reads it.

On-device scheduling was not removed; the server notification came alongside it, not instead of it, because push can fail silently. The reminder screen states the limit in its own words: if you do not open the app for a long time and the notification does not arrive either, you may not learn about a change made in the meantime. Server notifications do not reach every device. On iPhone they do not work at all — there is no iOS build of the app. On Android they require Google Play Services: every Huawei model sold in China and some Xiaomi, Oppo and Vivo models ship without that component, and notifications never reach those phones. The rest of the app works normally there, and the student is told why.

App lock

If the student wants, they lock the app with their own face; with their own engine on their own device, using the same template. It is optional, and turning it off does not affect attendance, attendance status or what the teacher sees.

Device eligibility test

At setup, whether the phone provides the required hardware guarantees is tested. If it does not, the student is routed to the teacher-approved path; no verification is left to fail silently.

Institution

The administrator side

Setting up, monitoring and closing a term. Setup steps are on the school page.

Start-of-term bulk upload

Teachers, courses, the weekly schedule, students and course enrolments are imported in a single upload. First a dry run is performed: without writing anything, it reports line by line which row would be rejected and why. The upload can be re-run; the same row does not produce a second record.

The upload screen states one thing plainly, and it is a functional rule rather than a warning notice: uploading a roster does not enable face verification. Consent is a separate step and it comes from the student themselves.

Usage reports

On the dashboard: distribution of methods, manual-marking and paper ratios, average participation, number of appeals, number of students who have never used the app, number of teachers who have opened a session.

These reports never touch the risk tables. The possibility of a risk score being misread as an “attendance status” is thereby removed entirely.

Multi-institution setup and the institution boundary

One installation can serve multiple institutions. Every query passes through the institution boundary and the filter is collected in a single place: one query that skips the boundary means a cross-institution leak.

Password reset and account closure

A teacher who forgets their password does not drop out of the system permanently: they request a reset and set a new password themselves. A departing teacher's account can be closed and reopened if needed, with the reason written to the audit log. An administrator cannot close their own account — this is not a convenience restriction but a guard against an irreversible action that would leave the institution with no administrator.

Password reset runs end to end: the “I forgot my password” link on the dashboard, the one-time link that arrives by email, and the password-setting page that link opens. The same response is returned for an address that is not registered — so that which email addresses exist in the system does not leak.

Account closure is done from the dashboard, and the screen states what it does not do as plainly as what it does: the person can no longer sign in, but the record is not deleted — past attendance records, receipts and the audit trail stay exactly as they were, and the account can be reopened. An administrator who confused closing with deleting would make the most expensive mistake available.

Withdrawal from a course

A student who withdraws from a course can be dropped from the enrolment and reinstated if necessary; otherwise they would be recorded absent every week for the rest of the term. It is done from the Section management screen on the dashboard; a reason is required and it is written to the audit log. Per ADR-009 the record is not deleted: only sessions after the withdrawal date are affected, the past stays as it was.

Student information system integration

CSV is always available to everyone. On top of that comes the route where the teacher opens their own SIS session on their own device: the server never connects to the SIS, the password never reaches the server, addresses are defined by hand, and enabling it requires a written institutional permission reference.

The reverse direction is not automatic: the attendance result is downloaded as a file and uploaded to the SIS by hand. The system has no endpoint that writes into the SIS, and this is the natural consequence of the decision not to store passwords.

A reference API specification that institutions can implement on their own side is published. There is no ready-made Moodle plugin and no LTI 1.3 integration — at this level the institution implements it on its own side.

Institution time zone

Day boundaries are computed in the institution's time zone; when the server's clock and the school's day diverge, classes close to midnight would be recorded on the wrong day. The computation uses this value everywhere.

The value is changed from the dashboard: the Institution time zone card on the Academic calendar tab. An invalid name is rejected and the record is left unchanged — had it fallen back to the default silently, the administrator would be told “saved” while reminders drifted to the wrong time unnoticed. The change requires a reason and is written to the audit log with both the old and the new value. If a broken value was written into the database by hand, the dashboard does not hide it. Changing the zone does not shift class times — 09:40 stays 09:40; what changes is which absolute moment that clock time maps to, and reminders on the student's phone are rebuilt the next time the app fetches the schedule, not immediately.

Academic calendar and public holidays

Days on which no teaching takes place can be defined at institution level; on those days no class hour is generated, no reminder is sent, no student comes in for nothing, and a class that never happened does not enter the absence denominator.

It is defined from the Academic calendar tab on the dashboard. Public holidays do not come preloaded — each institution has to enter its own days.

Reading the audit log

An audit log nobody can read is not an audit log: a table that records who looked at which student's risk history, when and with what stated reason, but that nobody can look at, is useless both in an appeal and in a data subject request. That is why the log is readable — and reading it itself also requires a reason and is written back to the audit log.

It is read from the Audit log tab on the dashboard; it can be filtered by action type and date.

Deciding appeals from the dashboard

An appeal is filed, reviewed and decided; an accepted appeal really does correct the attendance record. The administrator decides appeals against a risk flag from the Risk engine tab on the dashboard.

Attendance appeals, on the other hand, are decided in the teacher app (see above) — because that decision belongs to the teacher of the course.

Email delivery trail

The outcome of every outgoing email (invitation, password reset, institution approval, support) is recorded; its content is not stored. Without this trail an invitation that could not be delivered would be visible nowhere: the teacher would say “the invitation never arrived”, the administrator would say “I sent it”, and both would be right.

It is read from the Audit log tab on the dashboard and it is partitioned by the institution boundary: an administrator sees only their own institution's delivery records. Records with no known institution — an email arising from the support form, say — are shown to no institution administrator at all. Assigning those to an institution would mean guessing it from the address, and that guess is the very leak we are closing.

Evidence

Receipts are verified without asking us

This is the product's most important decision. If the validity of a receipt can only be checked by the server that produced it, that receipt is not evidence against the server.

Asymmetric receipt signature

New receipts are signed with Ed25519. The public keys needed to verify a receipt are published from a public endpoint that requires no authentication.

Requiring no identity is deliberate: if you had to prove your identity to the server before obtaining the key, verification would depend on the server's permission — that is, it would not be independent.

We are not skipping one deployment detail: this depends on a signing key having been configured at installation. In an installation upgraded from the old symmetric key ring, receipts continue to be signed the old way and cannot be verified independently until the new key is configured. This is a deployment step and the key is in the operator's hands. Which case applies is stated by the receipt itself.

Verifying the receipt on the student's phone

A verifiable signature with no tool to verify it remains a promise. The student app does this itself: it fetches the public keys from the public endpoint, stores them on disk and checks the signature of every receipt on the device. Once the keys have been fetched, verification also works offline.

The result is not a “valid/invalid” switch but three distinct states: signature verified, signature invalid (the content has been tampered with) and could not be verified (the key could not be fetched, or the receipt is symmetrically signed). Collapsing the three into one flag would be the most expensive mistake: “I could not download the key” is a network problem, “the signature does not match” is an accusation. The screen never shows them as the same thing and never calls the “could not be verified” state fraud.

Old receipts are marked honestly

Old receipts signed with a symmetric key cannot be verified independently, and every receipt says so on its face. A verifier can answer the question “why can this receipt not be verified independently?” without asking the server. The secret key itself is never published — that would be handing everyone the ability to fabricate receipts.

Verifying the device signature

The student's device signs the proof with a hardware-bound key and the server verifies that signature. Storing the signature is not enough: a signature that is stored but never verified would make the flow look as if it worked, because the field was populated. The key's “requires user authentication on every use” constraint is also read from the attestation data rather than from the client's assertion.

But the default mode is still permissive: proof that arrives unsigned is not rejected today, it is flagged. Moving to enforcing mode is a configuration decision, and an institution should only make it after it knows its own device fleet. The attestation chain is verified up to the manufacturer's root certificate, and the result is recorded in four states: no chain · could not be verified · tampered · verified. The root certificates are pinned inside the software — they are never fetched over the network at run time, because that would make attendance in a classroom depend on a working connection. “Could not be verified” is not an accusation: a phone that cannot produce an attestation certificate, a manufacturer root we do not recognise, or a stale revocation list all land there. But no state rejects the proof — even a chain that looks tampered with is flagged and accepted. This is the deliberately open point: the chain is verified, but nothing is done with the result.

Session root and reconciliation

When the class ends, a signed digest of all receipts is published and the number of receipts is compared with the number of verifications. If there is a discrepancy, the loss is on the system's side and it is visible before the student files an appeal.

Risk engine

The engine runs, but in shadow mode: it writes signals and takes no action. The weights have not been measured in the field; they are reasoned guesses.

Which signal counts is on the record and off by default — a rule nobody has decided on does not run silently. Even turning a rule on does not take the engine out of shadow mode: at most it makes the rule enter the score and become visible to the administrator; it cannot change any attendance result.

Limits

The product's limits

This section is the continuation of the list above, not a footnote to it: an institution makes its deployment decision knowing these.

  • The apps run on Android. Both the teacher and the student app target Android. A student on an iPhone has their attendance taken through the teacher-approved route — which is a fully valid attendance record; server notifications, however, never reach those phones.
  • The room headcount is off by default. Described above; we repeat it here because assuming it is on would be one of the most expensive misunderstandings. It never runs unless the teacher starts it explicitly, and it changes no attendance record.
  • Continuity is measured by rounds, not by a presence beacon. The student's device does not broadcast a continuous presence signal; the proximity architecture runs in the opposite direction — the teacher's device broadcasts, the student's device listens. Staying in the room for the whole class is therefore measured by unannounced rounds during class and by the stay-duration threshold.
  • There is no institutional single sign-on (SSO). Accounts are opened with an invitation and a password. For a large university's IT department this is usually a gap, and we know it.
  • There is no ready-made learning management system plugin. Integration runs by three routes: CSV, the SIS session the teacher opens on their own device, and the reference API institutions can implement on their own side. Moodle and LTI 1.3 sit at that third level.
  • Deletion of risk signals is scheduled at installation. Signals drop out of evaluation after a certain period; the maintenance job that actually deletes the records does not arrive scheduled — whoever performs the installation has to define it as a job. The system also separately reports whether any expired records are still sitting there.

It would have been easy to shorten this list. The shortened version would have collapsed at the first deployment meeting — and taken with it all the trust built up until that meeting.