# Business rules — bigpond_frontend

Evidence date: 2026-10-19.

Source: `bigpond_frontend` branch `master` at `c5ef2b1`.

Rules below are taken from code or from `README.md` / `CHANGELOG.md`. Nothing here is an approved policy document. The repository does not contain a business-rules file.

Labels:

- **Documented** — written in README, changelog, or an in-repo comment as intended behaviour.
- **Implemented** — what the code does on this commit.
- **Inferred** — a reasonable reading of why the code is shaped this way. Not a confirmed policy.

## Identity and access

### BR-001 — Two product names

**Documented.** The landing page title is “Welcome to Audit Whizz”. `README.md` and `package.json` call the product BigPond.

**Requires Confirmation.** Which name is the customer-facing name.

### BR-002 — People cannot self-register

**Implemented.** `Authenticator` is rendered with `hideSignUp={true}`.

**Implemented.** An administrator creates the Cognito user with `AdminCreateUser` and adds group `Admin` or `User`, then inserts a MySQL `users` row.

### BR-003 — A Cognito account is not enough

**Implemented.** After sign-in the app calls `/initUser` with the email. The Lambda selects `users` by email. If that query returns no row, it throws. The UI then has `role_id === ""` and shows `Loader`.

**Inferred.** The MySQL row is the application account. Cognito only proves the email can sign in.

### BR-004 — Inactive users are blocked

**Implemented.** When `currentUser.active === 0` the app renders “Your User is Inactive, Please Contact Your Administrator” and a logout button. No other routes render.

### BR-005 — Only two roles open the app

**Implemented.**

| `users.role_id` | Shell | Default route |
|---|---|---|
| `1` | Admin | `/companies` |
| `3` | User | `/clients` |
| anything else, including empty | Loader | none |

**Implemented.** Cognito group is `Admin` only when the role sent to `signUp` is the string `"1"`. Every other value, including numeric `1` if it were not a string, goes to group `User`. The add-user dropdown uses `value={1}` and `value={3}` on `<option>`, which the DOM submits as strings. **Inferred** that the group check matches the dropdown.

**Unknown.** What `user_role` rows exist. The API can list and edit that table. The add-user form only offers 1 and 3.

### BR-006 — Admins see every firm; users see their own clients

**Implemented.** Client list uses `/getcompanyclients` when `role_id === 1`, otherwise `/getclientsbycompanyid/{company_id}`.

**Implemented.** Company and user screens are not routed for role 3.

**Implemented.** The Express handlers do not check the caller's role. This is an enforcement gap, not a second business rule. **Requires Confirmation** whether API-level role checks are required.

### BR-007 — Logs are an admin capability, and the menu hides them

**Implemented.** `/logs` is registered only in the role 1 route table.

**Documented** (`CHANGELOG.md`). A Logs link was added to admin and user navigation.

**Implemented.** `NavAdmin.jsx` comments the Logs link out. `NavUser.jsx` has no Logs link.

**Requires Confirmation.** Whether hiding Logs is intentional.

## Audit firms

### BR-008 — A firm has a name and an active flag

**Implemented.** `company` columns written by the API: `name`, `active`, `created_by`, `created_at`, `updated_by`, `updated_at`.

**Implemented.** Add form: name required. Name compared case-insensitively to firms already loaded. Duplicate message: “Company name already exists.”

**Implemented.** Delete is `DELETE FROM company`. No cascade is written in this handler.

**Inferred.** “Audit Firms” in the nav and `company` in the database are the same entity.

### BR-009 — New users are attached to a firm

**Implemented.** Admin must select a company. Insert writes `company_id`.

**Implemented.** Bulk import resolves `company_name` to an id by case-insensitive match against loaded companies. Unknown names are row errors.

## Clients

### BR-010 — A client belongs to one firm and has a contact

**Implemented.** `company_client` insert columns: `company_id`, `name`, `contact_email`, `contact_name`, `active`, audit columns.

**Implemented.** Add form rejects empty client name, empty contact name, and an email that fails the form's regex.

### BR-011 — Client dropdowns only list active clients

**Implemented.** `authContext.getClients` keeps rows where `active === 1` before storing them for Xero Testing.

### BR-012 — Deactivating a client disconnects Xero

**Implemented.** On `updatecompanyclient`, if `active === 0`, the handler reads `xero_token` and `connection_id` and deletes `https://api.xero.com/connections/{connection_id}`.

**Implemented.** If that HTTP call throws, the error is logged and the client update response is still success.

### BR-013 — Xero tokens are stored on the client and copied by contact email

**Implemented.** `updatecompanyxero` sets `xero_token`, `xero_refresh_token`, `xero_tenant_id`, `connection_id`, and `active = 1` on the client id. It then copies `xero_token` and `xero_refresh_token` to every `company_client` with the same `contact_email`.

**Inferred.** One Xero login can cover several client rows that share a contact email. **Requires Confirmation** that this copy is intended.

### BR-014 — Xero redirect host is production

**Implemented.** Redirect URI is `https://www.auditwhizz.net/callback` in `XeroExpress` and `xerotoken`.

## Users

### BR-015 — New application users start as Expense Document users

**Implemented.** `/adduser` and `/bulkaddusers` insert `workflow_type` as the literal `"Expense Document"`.

### BR-016 — Bulk import creates active standard users only

**Implemented.** CSV columns: `company_name`, `fullname`, `email`. Parser sets `role_id = 3`.

**Documented** (`CHANGELOG.md`). Defaults are `role_id=3` and `active=1`.

**Implemented.** `/bulkaddusers` inserts each user independently. A failed row increments `failed` and later rows still run. There is no transaction around the batch.

### BR-017 — User email is the Cognito username

**Implemented.** `AdminCreateUser` `Username` is the email. Auth parameters set `usernameAttributes` to email.

**Implemented.** Add-user email regex allows a 2 to 4 character TLD.

### BR-018 — Duplicate Cognito user is reported, not overwritten

**Implemented.** `UsernameExistsException` sets a message that the user is already registered on Cognito and should check email. The exact string is in `addUser.jsx`.

### BR-019 — Deletes are hard deletes

**Implemented.** User, company, client, user role, and custom match flow deletes are SQL `DELETE`, not an `active` flag.

**Inferred.** “Active” is a separate status. Delete removes the row. **Requires Confirmation** that hard delete is the approved rule for firms and users.

## Xero Testing

### BR-020 — Two workflow types

**Implemented.** Dropdown values: `Expense Document`, `Revenue Document`. Changing the value posts `/updateuserworkflowtype` when it differs from the current value.

### BR-021 — Expense Document expects Excel and invoice files before generate waits on extracts

**Implemented.** `canGenerateWorkFlow`:

- Expense Document with at least one `.xlsx` and one PDF: keep waiting until JSON file count is at least the PDF count, then allow generate.
- Any other combination: set can-generate true immediately.

**Inferred.** The wait is for per-PDF extract output. The else branch also allows generate when Expense Document is missing Excel or PDF. That may be unintentional. **Requires Confirmation.**

### BR-022 — File types and counts

**Implemented.**

- Excel: `.xlsx`, max 1, header “2. TRANSACTIONS TO BE REVIEWED - EXCEL UPLOAD”.
- Invoices, only for Expense Document: `.pdf`, `.png`, `.jpeg`, `.tiff`, max 50.
- Bank CSV: `.csv`, max 50. Header number is 4 for Expense Document and 3 otherwise.
- CSV requires a selected bank. Options come from distinct `bank_number` values already on the session table.

### BR-023 — Uploads are private to the Cognito identity

**Implemented.** Upload components set `accessLevel={"private"}`.

**Implemented.** Several download and zip paths use bucket `big-pond-uploads-bucket123209-dev` and keys under `private/{identityId}/`.

## Workpapers

### BR-024 — A flow is a sequence of typed document steps

**Implemented.** Step types: Statement, Invoices, Supporting Document, Custom (`STEP_TYPES`).

**Implemented.** Preset fields exist for invoices (company, date, invoice number, amount, GST%, total), statements (company, invoice number, amount, date, balances, description, debit, credit), and supporting documents.

### BR-025 — Matching tolerances

**Documented** (`CHANGELOG.md` and `README.md`, 2025-08-08):

- Amount within 1% or up to 5.00.
- Date within ±7 days.
- Identifiers exact or close when present on both sides; do not penalise if missing on one side.
- Entity names normalised, abbreviations allowed.
- Confidence at least 6 when filtering is on, at least 8 otherwise.

**Implemented** in `buildLargeDocumentPrompt`:

- Amount: within 1% or absolute difference up to 5.00, “whichever is stricter”.
- Date: up to ±7 days.
- Identifier and entity rules match the changelog wording.
- Confidence threshold is the constant 6.
- Filtering stance text is always “light filtering – include plausible candidates at confidence ≥ 6”. `isFiltering` only adds a “Filter criteria” sentence.

**Requires Confirmation.** Whether the live rule is “always 6” or the changelog's 6-versus-8 split.

### BR-026 — One record may match many

**Implemented** as prompt text: one record can match multiple records (one contract to many payslips, many payments to one invoice).

This is an instruction to the model, not a database constraint.

### BR-027 — Named flows belong to a company

**Implemented.** `custom_match_flows.company_id` is required on insert. List is `where company_id = ?`. The UI requests `currentUser.company_id`.

**Implemented.** Delete is a hard delete by id with no company check in the SQL.

### BR-028 — Exports follow the step chain

**Documented** (`CHANGELOG.md`). Export starts at step 1, walks matches through later steps, and places a single unmatched section at the end. Both the on-screen Excel export and the S3 export were updated.

**Implemented** files named by the changelog: `ResultsTable.jsx`, `exportExcelToS3.js`.

### BR-029 — Dates and amounts in model output

**Implemented** as prompt formatting rules:

- Dates `dd/mm/yyyy`.
- Amounts `xxx,xxx.xx` with no symbols and no negative signs.
- Missing values `"-"`.
- No rounding.

**Documented** separately in changelog `[Unreleased]`: Excel field detection should trust Excel's own date, text, and number formats rather than guessing. Code for that detection is in `customMatchPage/utils/formatters.js` and `StepCard.jsx`. Treat the philosophy paragraph in the changelog as **Documented**. The detector functions exist. This pass did not re-prove every branch.

### BR-030 — localStorage writes for match state are queued

**Documented** (`README.md`) and **Implemented** (`safeLocalStorage.js`). Keys `custom_match_page`, `result_table_db`, `match_table_db`, and `match_results` go through a per-key queue.

**Documented** in README and not confirmed in code during this pass: version `v2.1`, 5MB maximum, 30-day temporary tables, 2-day flow retention, 300ms debounce, JSON Schema validation. **Unknown** whether those limits are enforced.

## Logs

### BR-031 — Log export requires a date range

**Implemented.** UI and `handleGetLogs` require `fromDate` and `toDate`. `fromDate` must be before or equal to `toDate`. Dates must parse.

**Implemented.** Optional filters are exact `user_name` and exact `audit_firm_name`.

**Implemented.** Rows come from `workpaper_logs` ordered by `created_at DESC`.

**Implemented.** The download is CSV, filename `workpaper_logs_{fromDate}_to_{toDate}.csv`.

**Documented.** Changelog says `file_id` and `table_name` are excluded. The SELECT list in `handleGetLogs` does not include those columns. It selects audit firm, user, operation, document, token counts, model, timing, success, error, step, and `created_at`.

## Contact and mail

### BR-032 — Contact messages go to one mailbox

**Implemented.** `contactmail` SES `ToAddresses` is `bhawin@auditwhizz.net`. Source is `no-reply@auditwhizz.net`.

**Implemented.** Name, email, and message are required. Email must match the contact form regex.

**Requires Confirmation.** Whether that mailbox is still the right recipient.

### BR-033 — Xero mail uses the same from-address

**Implemented.** `sendxeroemail` sets `fromUrl` / source to `no-reply@auditwhizz.net`.

## Data handling

### BR-034 — Session tables are created per identity

**Implemented.** Table name is `master_table_` plus the identity id with hyphens replaced by underscores. Created with `CREATE TABLE IF NOT EXISTS` on every successful `initUser`.

**Inferred.** One Cognito identity has one working table for Xero Testing. **Requires Confirmation** of retention and of what happens when the identity id changes.

### BR-035 — CORS allows the production site and local dev

**Implemented.** `bigpondexpress`, `contactmail`, `frontendLogs`, and `deleteSessionUploads` allow `https://www.auditwhizz.net` and `http://localhost:3000`.

Other Lambdas allow `https://www.auditwhizz.net` only (for example `openAIFileProcessing`, `manageFileProcessTable`).

### BR-036 — Password policy

**Documented** in Amplify auth parameters (imported pool): minimum length 8, lowercase, uppercase, numbers, symbols. MFA off. Email is auto-verified.

Whether the live user pool still matches the imported parameter file is **Requires Confirmation**.

## Conflicts to resolve

| Topic | Documented | Implemented |
|---|---|---|
| Match confidence cutoff | `≥ 8` when not filtering, `≥ 6` when filtering | Constant `6`, stance text always light filtering |
| Logs in the menu | Admin and user nav | Admin route only, link commented out |
| Product name | BigPond and Audit Whizz | Both |
| Model | README says GPT-4o | `gpt-4.1` in `openAIFileProcessing`, `gpt-4o` in `processUnprocessedFiles` and as a StepCard fallback |
| OpenAI key | README lists a React env var | Lambda reads `process.env.REACT_APP_OPENAI_API_KEY`; another function declares secret `OpenAIKey` |
