# PRD — ieb-inde-3 (IEB examination authoring)

Evidence date: 2026-10-13. Inspected revision: `main` at `ce71caf`.

Provenance labels: **Documented** (readme or config text), **Implemented** (code path), **Inferred** (reasonable reading of that code), **Unknown**.

The website root used below is `INDE-3/Evolution Software Unified Framework/Evolution Software Unified Framework/core_custom/INDE-3/`.

## Product purpose

`INDE-3/ReadME.txt` does not name INDE-3. It names **Exams = INDE-1**. The setup section includes INDE-3 in "All Projects (INDE-1 … INDE-7)". **Documented.**

The implemented custom website keeps examination subjects, document packs, internal moderation, and external moderation. Status mail uses the subject prefix `IEB:` and the sender label `System Notice :: IEB Exams`. **Implemented.**

Calling this product "Exams" is **Inferred** from that mail label and from the surface names. The readme attaches the word Exams to a different code. **Unknown / Requires Confirmation.**

The same website still contains the framework's admin, catalogue, loyalty, medical, and sales pages. Those files are present. This PRD does not treat them as examination requirements unless a custom INDE-3 class calls them.

`controls/module_custom/billingInstructions.ascx.cs` models landlord, tenant, and supplier billing rows. No examination form was found calling it. It is framework leftover in this tree, not an INDE-3 requirement.

## Users and roles

`business/enums/pNums.cs` defines framework user types:

| Value | Name |
|---|---|
| 1 | WebsiteUser |
| 2 | PowerUser |
| 3 | SuperUser |
| 4 | AdminUser |
| 5 | CustomUser |

**Implemented.**

`examNums.SubjectResourceRole` defines examination roles stored on the subject. The form compares them to `oUser.recId`:

| Value | Name | Stored on |
|---|---|---|
| 1 | Examiner | `SubjectOverview_SubjectResources_Examiner` |
| 2 | Reviewer | `SubjectOverview_SubjectResources_Reviewer` |
| 3 | InternalModerator | `SubjectOverview_SubjectResources_InternalModerator` |
| 4 | ExternalModerator | `SubjectOverview_SubjectResources_ExternalModerator` |
| 5 | AssessmentSpecialist | `SubjectOverview_SubjectResources_AssessmentSpecialist` |

`GetSubjectResourceRole` walks that list with `else if`. The first match wins. A person who is both examiner and reviewer is treated as examiner. **Implemented.** **Inferred** that dual assignment was not a designed case.

How the framework combines `userTypes`, `isRestricted`, and `customUsers` at page-render time was not fully traced. **Unknown / Requires Confirmation.**

## Major workflows

### 1. Open an existing examination subject

Trigger: populate of Examination Authoring and Moderation. Custom region dated 2018-02-06. `ExaminationAuthoringandModeration_form.ascx.cs` `PerformCustomPopulateAddOn`.

- Subject code is disabled.
- If the document password is empty, it is filled with `RandomString(4)` + a number from 0 to 1000 + `RandomString(4)`, and the field is read-only. A button copies it to the clipboard.
- Once the surface item exists (`recId > 0`), level, paper, subject name, type, and year are disabled.
- A user below SuperUser cannot edit the five resource picklists, and both save buttons are hidden.
- The status picklist is enabled only when the current user's `recId` equals the reviewer field. Otherwise it is disabled.
- The new document-pack button (child surface `3037`) is visible for examiner and reviewer. It is hidden for internal moderator, external moderator, and assessment specialist.

`PerformCustomSaveAddOn` on this form is empty and returns false. **Implemented.**

### 2. Change subject status

Trigger: save of the same form, custom region dated 2018-02-08, before the generated status assignment.

The block runs only when the posted status differs from the loaded object, the current user is the reviewer, and the item already exists.

It loads the active document pack with the highest `DocumentPackInformation_Revision`. If none exists, the block does not write a log, does not stamp `TimeSinceStatusChange`, and does not send status mail. The generated assignment after the block still copies the selected status onto the object. **Implemented.**

When a pack exists, the note title and field are:

| New status | Log title | Field id |
|---|---|---|
| EM compliant, minor changes (`1107`) | External Moderation Log | ExternalLog `94` |
| EM compliant, translation ready (`1108`) | External Moderation Log | ExternalLog `94` |
| EM moderation required (`1088`) | External Moderation Log | ExternalLog `94` |
| EM not compliant (`1106`) | External Moderation Log | ExternalLog `94` |
| Every other status in the switch | Internal Moderation Log | InternalLog `93` |

The note caption is an HTML table: updated by, document pack, revision, and the selected status text. `SaveCollaborationLog` stores the note and emails the reviewer. Then the object status and `TimeSinceStatusChange` are updated, and `EmailExamStatus` sends the status mail.

### 3. Create a document pack number

Trigger: populate of the document-pack form, the exam-documents form, the internal-moderation form, and the external-moderation form. The four copies of this block match. Dated 2018-02-06.

Document pack and revision are disabled.

If revision text is `0`, revision becomes one more than `Count(*)` of active `publ_ExaminationDocumentPack` rows for this parent. The count is a concatenated SQL string. Any other revision text is parsed as an integer.

If the pack number is empty, it is built from the parent subject:

`{year}-{Y or S}-{G12|G11|G10|G9|G8|G0}-{P1|P2|P3|P4|P5|P6|P0}-{subject code}-Rev{n}`

- Type display `year-end` becomes `Y`. Any other display, including a missing lookup, becomes `S`.
- Level displays grade 12 down to grade 8 map to `G12` … `G8`. Anything else becomes `G0`.
- Paper displays paper 1 to paper 6 map to `P1` … `P6`. Anything else becomes `P0`.

If the surface item is new (`recId == 0`), populate calls `PerformSave` immediately. **Implemented.** Opening a new pack therefore writes a row before the user clicks save.

`PerformCustomSaveAddOn` on these forms is empty.

### 4. Mark examination documents uploaded

Trigger: button `ExaminationDocuments_ExaminationDocuments_DocumentUploadComplete` on `ExaminationDocumentPack_ExamDocs_form.ascx.cs`.

Populate shows that button, and the new-document button for child surface `3038`, only when the current user is the parent subject's examiner or reviewer.

The click handler:

- Examiner: sets subject status to Examiner uploaded, review ready (`1083`), log message "Examination documents uploaded".
- Reviewer: sets subject status to Reviewer uploaded, IM release ready (`1084`), log message "Revised examination documents uploaded".
- Stamps `TimeSinceStatusChange`, saves the subject, calls `EmailExamStatus`, and writes an internal collaboration log (`fieldName` 93) whose entity id is the parent subject.

Status `1084` is a no-mail case inside `EmailExamStatus`. The reviewer upload still calls it. **Implemented.**

The same button switch also progress-saves internal-moderation criterion groups. Those cases call `PerformSave` and do not call `examHandler`.

### 5. Moderate a document pack

Trigger: populate of the internal or external moderation form.

`SetState(true)` shows `saveGroup`, the criterion feedback "new" buttons, and the progress-save / save-moderation buttons. `SetState(false)` hides them.

- External form: `SetState(true)` only for the external moderator. Every other role, and a user with no role, gets `SetState(false)`.
- Internal form: `SetState(true)` only for the internal moderator.

After that, both forms hide `btnSave` and `btnSaveDropdown`.

External criterion feedback buttons use child surfaces `3088` through `3100`. Internal buttons use `3062` through `3074`.

When the pack entity exists, the form fills `lblModerationSummary` from `ReturnComplianceSummary`. External passes `isExternal = true` and the external moderator user. Internal passes `false` and the internal moderator user. The summary is built only when the overall compliance-summary field is greater than zero (`EM12` or `IM12`).

`CurrentRole` caches `GetSubjectResourceRole` in view state. Dated 2018-02-15. Progress-save handlers on the external form check `CurrentRole` against external moderator before acting. The internal form checks internal moderator. **Implemented** at the call sites that were read.

`OpenCriterion` on the external form loads `CriterionFeedback` into a modal. Dated 2018-02-07 in the surrounding region.

### 6. List subjects the user is assigned to

Trigger: the examination authoring grid filter. Dated 2018-02-08.

If `user.userType` is below SuperUser, the filter requires `user.userDisplay` to equal one of the five resource columns (examiner, internal moderator, reviewer, external moderator, assessment specialist).

The subject form writes those columns from picklist values compared later as `recId`. The grid compares `userDisplay`. Whether the published column holds an id or a display name is **Unknown / Requires Confirmation**. If it holds the id, this filter does not match the value the form stores. **Implemented** as written. **Inferred** conflict.

SuperUser and above do not get this extra filter.

Subject Management populate has no INDE-3 custom block. Its only custom branch checks setup code `STUD-1`. **Implemented** leftover. It is not an INDE-3 subject rule.

## Functional requirements discovered from the implementation

| ID | Requirement | Provenance |
|---|---|---|
| FR-001 | The site keeps an examination subject with year, level, paper, type, subject code, subject name, status, document password, status-change time, and five resource roles. | Implemented. `oExaminationAuthoringandModeration` |
| FR-002 | A new empty document password is generated on populate and is read-only. | Implemented |
| FR-003 | After the subject exists, identity fields are locked. Users below SuperUser cannot change resource roles or use the main save buttons. | Implemented |
| FR-004 | Only the reviewer can edit the status picklist. | Implemented |
| FR-005 | A reviewer status change writes a collaboration note and sends status mail when an active document pack exists. | Implemented |
| FR-006 | Document pack numbers follow the year / type / level / paper / subject code / revision pattern above. | Implemented |
| FR-007 | Examiner or reviewer can mark documents uploaded, which sets status `1083` or `1084` and writes an internal log. | Implemented |
| FR-008 | Only the matching moderator sees criterion editing controls on the internal or external form. | Implemented |
| FR-009 | Compliance is recorded as one of four criterion statuses and one of four overall statuses, with the lookup ids in `examNums`. | Implemented |
| FR-010 | Criterion feedback rows are read through `sp_INDE_GetCriterionFeedbackData`. | Implemented call. Procedure body not in this repo |
| FR-011 | Users below SuperUser see only subjects whose resource columns equal `user.userDisplay`. | Implemented. Match against stored ids is unconfirmed |
| FR-012 | Subject Management stores level, subject code, and subject name, and status mail looks up the subject name from that row. | Implemented |

## Integrations

| System | Trigger | Direction | Notes |
|---|---|---|---|
| SQL Server `Framework_INDE-3_Demo` | Every surface read and save | Out | Active `conn`. Credentials omitted. Commented alternate catalog `Framework_INDE-3` on the local server |
| SMTP | `EmailExamStatus`, `SaveCollaborationLog` | Out | Amazon SES host in `eu-west-1`, port `2587`. From address `ieb.web@assetonline.co.za`. BCC is the `bcc` app setting. From display address on the message is `setup.communicationEmail` |
| Framework templates | Status and log mail | Read | Names `Examination Moderation Status` and `Examination Moderation Log`, type OutboundResponse. Bodies are database rows |
| WCF `framework_service` | Framework client | Out | `http://localhost:58913/framework_service.svc`. No live host name was found in this `Web.config` endpoint list |
| SMS SendEasy | Framework client | Out | `http://sms.netgen.co.za/SMS_V2.asmx`. Not called by `examHandler` |
| MediSwitch | Framework client | Out | Three QA gateway URLs. `MediSwitchResponses` = `0`. Not called by `examHandler` |
| reCAPTCHA | Config only in the settings that were read | Out | `siteverify` URL and keys are present. No examination form call was traced |
| Country ASMX | Config | Out | `www.webservicex.net` country methods |

`WebAddy` is `http://localhost:35474`. Status mail rewrites image sources and `{WebAddress}` with that value. A production public URL is **Unknown / Requires Confirmation**.

## Status mail rules

`EmailExamStatus` chooses recipients from the new status. Template `Examination Moderation Status`. Subject `IEB: {subject name} ({status display})`. Sender label `System Notice :: IEB Exams`.

| Status | Id | Recipients |
|---|---|---|
| No activity | 1093 | None |
| Examiner uploaded, review ready | 1083 | Reviewer |
| Reviewer query | 2160 | Examiner |
| Reviewer uploaded, IM release ready | 1084 | None |
| IM moderation required | 1085 | Internal moderator |
| IM not compliant | 1086 | Reviewer, then examiner, joined with `;` |
| IM compliant, EM release ready | 1087 | Reviewer, then examiner |
| EM moderation required | 1088 | External moderator |
| EM not compliant | 1106 | Reviewer, then examiner |
| EM compliant, minor changes | 1107 | Reviewer, then examiner |
| EM compliant, translation ready | 1108 | Reviewer, then examiner |
| Post-translation change required | 1109 | None |
| Print ready | 1110 | None |

The body includes subject, status, the newest active document pack and revision, updated-by, and the status-change time. Mail is sent only when a recipient address was set. A commented line would have forced a personal mailbox; the active path uses the role address. That address is not repeated here.

`SaveCollaborationLog` always addresses the reviewer, with template `Examination Moderation Log`, subject `IEB: {subject name} - Log Update by System Post`.

## Constraints

- Session timeout on the INDE-3 website is 20 minutes. **Implemented** in that `Web.config`.
- `httpRuntime maxRequestLength` is `10048` (about 10 MB).
- `compilation debug` is `true`. `customErrors` mode is `Off`.
- `defaultCulture` is `en-ZA`.
- `AllowCustomProgress` is `false`.
- Canvas and surface definitions, lookup displays (grade, paper, year-end), and mail templates are data, not code. The pack-number abbreviations depend on those lookup display strings matching exactly, case-insensitive.

## Error behaviour

Custom blocks catch exceptions, call `exception.HandleException`, and redirect to `/error`. The handler's own catches use the prefix `examHanlder` (spelled that way) and do not rethrow. A failed status mail returns false and does not by itself redirect. **Implemented.** Where `exception.HandleException` writes is the framework type. **Unknown.**

## Explicitly not requirements

- Rocket.Chat. No helper or setting was found under `core_custom/INDE-3`.
- Schools, clusters, or collaborative-problem-solving teams. Those surfaces are not in this website's `surface_custom` folder.
- Billing instructions. The control is present and unused by the examination forms that were read.
- A deployable production URL, a scheduler job for examinations, or an INDE-3 SQL publish script. None of those is in the tree.
