# Creating your own sample data

Two ways to do this, for two different jobs.

**Walk one record through by hand** — best for understanding how the system fits
together, and for producing a specific case you want to demonstrate. Takes about
ten minutes.

**Change the seed** — best when you want a hundred of something, or want your
data to come back after every reset.

Everything below was run against this build before it was written.

---

## Starting from an empty database

```bash
pnpm db:clear-demo
```

Shows what it would remove and changes nothing. Add `--apply` to do it.

This is not `pnpm db:reset`. A reset drops everything and puts the seed back;
this clears the demonstration content and keeps the configuration that has no
create screen behind it — permissions, roles, the institution and its settings,
academic years and terms, admission tracks, grade scales, lead sources, rooms
and notification templates. Wiping those would leave a system that cannot be
filled in again from inside itself.

Every user account goes too, and one superadmin is recreated
(`superadmin@example.local`, same password) because a database nobody can sign
in to is not a starting point. Reference numbers restart at `000001`.

**What you can rebuild by hand afterwards:** colleges, departments, programmes,
users, enquiries, and applications through to an admission decision.

**What you cannot:** courses, curricula, sections, academic years, terms, rooms
and fee schedules have no create screen. The clear keeps years, terms and rooms
for that reason — but courses, curricula, sections and fee schedules hang off
programmes, so they go when the catalogue does. Until those screens exist, a
cleared database can demonstrate admissions but not enrolment.

Run `pnpm db:reset` to put the full seed back.

---

## Before you start

The app runs at <http://localhost:3100>. Every demo account uses the password
`Demo!Pass123`.

You will sign in as different people as you go, because the whole point of the
system is that no single person can do all of this. That is not an obstacle to
work around — it is the thing being demonstrated.

| Step | Sign in as |
| --- | --- |
| 1–5 | `admissions@example.local` |
| 6 | `dean.nursing@example.local` |
| 7–9 | `registrar@example.local` |
| 10 | `cashier@example.local` |
| 11 | `faculty@example.local` |
| 12 | `registrar@example.local` |
| 13 | the student themselves |

---

## Path A — one record, by hand

### 1. Record an enquiry

**Admissions → Leads → Record an enquiry.**

Fill in a name and either an email or a mobile number. Pick how they reached you
and, if they said so, tick the consent box.

Leave consent unticked deliberately at least once and notice what happens: the
enquiry still saves. It can be worked as an enquiry but not added to a marketing
send, because nobody recorded that this person agreed to be contacted.

If you enter contact details that already exist, a warning appears naming the
existing enquiry — and still lets you save. Two people can share a family
mailbox.

### 2. Convert it to an application

Open the lead you just made and use **Convert to applicant**.

Confirm the programme — it is filled in from the enquiry if you recorded one —
and the applicant type, which decides the admission track and so which
requirements they will owe. You land on the new application, already submitted
and carrying a reference number.

This links rather than copies. The lead stays where it is, keeping its source
and first-touch date, so the admissions funnel still reconciles afterwards. Its
stage moves to *Application submitted* on its own; the application drives the
funnel, never the other way round.

The application is created as **submitted**, not as a draft. A draft belongs to
the applicant, who alone can finish and send it, and nobody is going to press
submit on behalf of somebody who walked in. Its requirement checklist starts
outstanding — which is exactly what a counter application looks like.

> **Or skip steps 1–2 entirely** and apply at <http://localhost:3100/apply>
> while signed out. That is the real seven-step wizard a member of the public
> uses. It autosaves as you go, and gives you a reference number at the end that
> you can track at `/apply/status` with the reference and surname.
>
> The public form is rate limited to five submissions an hour from one address.
> If you are making several, use the staff path above instead.

### 3. Move it through review

**Admissions → Applications**, open yours, and use the workflow actions in the
sidebar.

Try an action that is not allowed yet. The refusal names what is outstanding
rather than saying "cannot proceed" — that is the difference between a queue
that moves and one that stalls.

### 4. Book them onto an entrance examination

Still on the application, use **Book an examination**.

If no sitting exists, create one first at **Admissions → Examinations → New
sitting**. Full sittings appear in the list but cannot be selected, so you can
see that a date exists and is simply full.

### 5. Record the result

**Admissions → Examinations**, open the sitting, and use the roster to mark
attendance and enter a score.

The pass or fail outcome is your explicit choice. No cut-off is configured and
none is inferred, because the institution has not published one — see
ASSUMPTIONS.md C2.

### 6. Interview them

**Admissions → Applications → Schedule an interview**, then sign in as the Dean
of the relevant college to conduct it at **College → Interviews**.

The evaluation will not submit without narrative comments. A rubric total with
no reasoning is not something an admission decision can rest on.

### 7. Accept them

Back as Admissions, record the decision on the application.

Check `/apply/status` with the reference number afterwards. The applicant can
see that a decision has been made without telephoning anybody.

### 8. Start their enrollment

**Registrar → Enrollment → Start an enrollment.**

Search for the student, choose a term, and start. Clicking twice is harmless: a
student has at most one enrollment per term, so it opens the existing one rather
than failing.

### 9. Validate it

Open the enrollment. The validation panel names every blocker — timetable
clashes, the unit cap, unmet prerequisites, full sections — and the Registrar
can record an override with a mandatory reason where the rule permits one.

Both the override and its reason stay on the record permanently.

### 10. Take a payment

**Cashier → Post a payment.** Find the student and record one.

The balance is shown before the form asks for an amount. It is derived from the
payment ledger every time it is read, never stored — a stored balance is a
balance that can be wrong, and a wrong balance is money.

Overpay deliberately and see what happens: the excess is recorded as a credit
rather than refused. The money was genuinely handed over.

### 11. Enter grades

Sign in as faculty. **Teaching → Grades**, open a class, and mark the students.

Marks are chosen from the configured scale, not typed. A free-text box invites
"1.6" or "passed", neither of which is on any scale and neither of which can be
averaged or put on a transcript.

Submit the sheet. The inputs disappear — from here a mark changes only through a
request that keeps the old value, the new value, the reason and the approver.

### 12. Publish them

Sign in as the Registrar. **Registrar → Grades**, and publish the submitted
sheet.

The faculty member who awarded the marks cannot do this themselves.

### 13. Look at it as the student

Sign in as the student and check **My grades**, **My finance** and **My
enrollment**.

Before step 12 the grades page said the marks were not yet released. Now they
are there. That boundary is enforced in the database query, so an unpublished
mark is never loaded at all.

---

## Path B — change the seed

Use this when you want volume, or want your data to survive a database reset.

The seed lives in `packages/db/prisma/seed/`, split into ordered steps:

| File | What it creates |
| --- | --- |
| `steps/01-institution.ts` | The institution, branding, settings |
| `steps/02-access.ts` | Roles, permissions, demo accounts |
| `steps/03-academics.ts` | Colleges, programmes, courses, terms |
| `steps/04-sections.ts` | Section offerings and their timetable |
| `steps/05-admissions-config.ts` | Requirement definitions, rubrics, exam sittings |
| `steps/06-pipeline.ts` | Leads and applications |
| `steps/07-students.ts` | Students and enrollments |
| `steps/08-finance.ts` | Assessments, payments, certificates |
| `steps/09-grades.ts` | Grade sheets and marks |
| `steps/10-services.ts` | Document requests, clearance, notifications |
| `steps/11-sequences.ts` | Aligns reference-number counters with the rows created |

Names, programmes and fee figures come from `seed/data/`.

### Change the volume

Most steps take a count from `seed/context.ts`. Change it, then reseed.

### Keep it reproducible

The seed uses a seeded PRNG, so the same `SEED_RANDOM_SEED` produces
byte-identical data on every machine. That is what lets tests assert on specific
names and amounts. Change the seed value in `.env` for a different-but-still-
reproducible dataset.

### Reseed

```bash
pnpm db:reset
```

**This destroys everything in the development database**, so Prisma will ask you
to confirm. Everything in there is fictional, but the confirmation exists so
nobody runs it against something that is not.

Then check the result:

```bash
pnpm db:verify
```

Ten invariants — no double-booked rooms, no over-allocated payments, every
assessment reconciling with its lines, unpublished grades actually existing to
test visibility against. If you change the seed and one of these fails, the seed
is producing something the application would consider impossible.

### If you add a new importer or a bulk insert

Update `steps/11-sequences.ts` so the reference-number counters start above the
rows you created. Miss this and the first real record collides on a unique
index — which is exactly what happened during Phase 1, and why that step exists.

---

## Two rules worth keeping

**Never use real student data.** Not for development, not for testing, not for a
demonstration. Everything in this system is fictional, every generated document
is watermarked unofficial, and the standing banner across the top says so. The
moment real records go in, all of that becomes a lie.

**Consent is never assumed.** If you are importing enquiries, the importer marks
them as having given no consent unless the file explicitly says otherwise. That
is deliberate — see ASSUMPTIONS.md C9 — and it is worth preserving in whatever
you build on top.
