# Product requirements — magusEvents

Evidence date: 2026-10-09. Inspected revision: `master` at `bb6e583` (2019-08-16).

Source: `git@bitbucket.org:overdrive/magusevents.git`.

No requirements document existed in the source repository or in `silicon_ark/magusEvents/` before this file. Every requirement below is recovered from the implementation. Where the code is the only source, the status line says **Implemented** and the confidence notes an inference. Nothing here is a **Documented Requirement** from a product author.

The assembly name is **MagusEvents**. The product the code implements is an on-site Windows scanner for one Footprint accreditation event at a time, with a zone-access screen and a meal-check screen.

## Users and roles

The repository does not define accounts, roles, or a login. Settings are not behind a code or password.

| Actor | Evidence | Status |
|---|---|---|
| Scanner operator | Uses the scan `TextBox`. Input ends on Tab or Enter (`Scan.xaml.cs` `ScanninAcc`, `Scanmeal.xaml.cs` `ScanninAcc`). A hardware wedge is **Inferred**. | Implemented |
| Settings operator | Opens settings from the SETTINGS control on either scan screen. No identity check. | Implemented |

There is no server session. API calls send no `Authorization` header.

## Startup

**FR-001 — Full-screen shell**

- Trigger: process start (`App.xaml` `StartupUri="MainWindow.xaml"`).
- Behaviour: a borderless window, 1920×1080, maximized, with a header frame and a body frame. `App.xaml.cs` has no startup logic.
- Evidence: `MainWindow.xaml`, `App.xaml`.
- Status: **Implemented**. Confidence: high.

**FR-002 — Choose the first body from the local database**

- Trigger: `MainWindow` constructor, after `WorkMode` is set to `ON`.
- Input: `Events` row where `Active = 1` (`Database.GetAccreditationID`). If several rows match, the last row read wins. There is no `ORDER BY`.
- Behaviour:
  - No active id: show `Setup`.
  - Active event with `IsMealZone = 1`: show `Scanmeal`.
  - Otherwise: show `Scan`.
- The header is loaded in every case.
- Evidence: `MainWindow.xaml.cs` `getbody`.
- Status: **Implemented**. Confidence: high.

Saving settings does not use `IsMealZone` to pick the next page. It uses `Setup.OPT`, which is `1` only when settings were opened from `Scanmeal`. After a first-time save, the app shows `Scan` even if the meal-zone box was checked. The meal screen is selected on the next process start. Same split for the image-download finish and the CSV import: both construct `Scan`. Evidence: `Setup.Save_Data`, `Setup.GetProfileImages`, `Setup.Import_CSV`. Status: **Implemented**. This split is easy to mistake for a bug. Confirmation is required before treating the post-save screen as the intended product rule.

## Header

**FR-003 — Show the active event and the clock**

- Trigger: header construct, and every header reload after save or back.
- Behaviour: if an active event exists, show `AccreditationName`, `DateFrom` (the date label is not a formatted range), `Location`, and, when `Logo` is non-empty, a bitmap decoded from that base64 string. The clock label is `HH:mm`. The date label is `dddd, d MMMM`. Both refresh once a second.
- Empty state: the XAML defaults remain `EVENT`, `VENUE`, `DATE`.
- Evidence: `header.xaml`, `header.xaml.cs` `getEVent`, `get_date`.
- Status: **Implemented**. Confidence: high.

**FR-004 — Exit confirmation**

- Trigger: the header exit image (`ExitApp`).
- Behaviour: "Are you sure you want to close the application?" Yes shuts the process down. No leaves it open.
- The settings screen has a separate exit handler that shuts down with no question (`Setup.Exit_App`).
- Evidence: `header.xaml.cs`, `Setup.xaml.cs`.
- Status: **Implemented**. Confidence: high.

**FR-005 — Connectivity dot**

- Trigger: the same one-second timer.
- Behaviour: `WebClient.OpenRead` on `https://www.footprintapp.net/api/tourn/`. Success paints the dot `#FF01CF0B` and sets the field `offline` to `1`. Failure paints `#F00` and sets `offline` to `0`. The field name and the value are opposite. Nothing else reads `offline`.
- The probe URL is a tournament path. Using it as "is the PC online" is **Inferred** from the colour change. The call itself is **Implemented**.
- Evidence: `header.xaml.cs`, `Webservices.CheckForInternetConnection`.
- Status: **Implemented**. Confidence: high for the call; the field name is misleading.

On first header load the code creates `c:\temp_acc\` if needed, and creates empty `meal.txt` and `scan.txt` if they are missing (`File.CreateText`, handle not stored).

## Online and offline mode

**FR-006 — Work mode**

- Default: `MainWindow` sets `App.Current.Properties["WorkMode"]` to `"ON"` on every start. The value is not written to disk.
- Settings toggle: checked means `"OFF"`, unchecked means `"ON"`. The control template highlights the ON caption when unchecked and the OFF caption when checked. Opening settings sets the toggle from the current property.
- Zone scan: `"ON"` starts two timers and shows "You are working online" in green. Any other value shows "You are working offline" in red and does not start the timers.
- Meal scan does not read `WorkMode` and does not refresh from the network.
- Evidence: `MainWindow.xaml.cs`, `Setup.ToggleButton_Click`, `App.xaml` `ToggleButtonStyle1`, `Scan.xaml.cs` constructor.
- Status: **Implemented**. Confidence: high.

## Settings and event selection

**FR-007 — Load the event list when the probe succeeds**

- Trigger: settings timer tick, first time only (`_loadedEvents` and `_loadedEventLoading` both 0).
- Behaviour: if `CheckForInternetConnection` is true, the label becomes "LOADING DATA ..." and the app GETs `https://footprintapp.net/api/Accreditation/getevents`. The completed handler stores the body and builds a selectable list. The active event, if its `AC` matches, is marked selected.
- If the probe fails, the label is "SELECT EVENT" and no local event list is loaded. `GetEventsDataLocalData` is commented out.
- Failure of the download is swallowed.
- Evidence: `Setup.get_time`, `Setup.GetEventsData`, `Setup.wb_DownloadStringCompleted`.
- Status: **Implemented**. Confidence: high.

**FR-008 — Save one active event and its zones**

- Trigger: the Save control (`Save_Data`).
- Input: the event chosen in the list, the meal-zone checkbox, the selected meal day (`Selected_mealdays`), the selected meal session (`Selected_meals`), and the in-memory zone list (from, to, lock, active, icon, `ZTID`).
- Behaviour:
  1. `UPDATE Events SET Active = '0' WHERE AC > '0'`.
  2. Update the chosen event, or insert it if the update reports no row. Saved columns include name, dates, location, logo, `MealDays` from JSON `TotalDays`, `MealsPerDay`, `DayNumber`, `SessionNumer`, `IsMealZone`, `Active = 1`.
  3. Update each zone, or insert it. `FromTime` and `ToTime` on the UPDATE have `:` removed. The INSERT uses the values before that removal.
  4. Reload the header and open `Scanmeal` when `OPT` is 1, otherwise `Scan`.
- `BarcodeLength`, `EventDate`, `EventTime`, `UpdateTriggerOne`, `UpdateTimeOne`, `UpdateTriggerTwo`, and `UpdateTimeTwo` exist on the table and are not written here.
- Exceptions are swallowed. The Save caption is set to "SAVING .." and back to "SAVE" only on the success path.
- Evidence: `Setup.Save_Data`, `Database.CreateTables_Events`.
- Status: **Implemented**. Confidence: high.

**FR-009 — Meal day and meal session buttons**

- Trigger: the event payload has `TotalDays` and `MealsPerDay`, and `ShowPage` builds the buttons (`numberofdays`).
- Behaviour: choosing a day stores `Selected_mealdays`. Choosing a meal stores `Selected_meals`. Those integers are what meal scanning later reads as `DayNumber` and `SessionNumer`.
- If the event is already stored and `IsMealZone` was 1, the checkbox is checked when the event page opens.
- Evidence: `Setup.ShowPage`, `Setup.numberofdays`, `Setup.MealDays_Selected_Button`, `Setup.MealPerDay_Selected_Button`.
- Status: **Implemented**. Confidence: high. The product meaning of "day" and "session" is **Inferred** from the meal screen title `MEALS (Day {n})`.

The settings XAML also shows a SYNC SCHEDULE block (static times and labels from "5 min" through "Hour"). No code-behind reads those controls. Status: **Implemented** as layout only.

`Reset_Data` is empty.

A TEST control exists on `Scan.xaml` and `Scanmeal.xaml` with `Visibility="Hidden"` and no click handler.

## Download and import

**FR-010 — Download applicants for the active event**

- Trigger: Download Data (`Download_Data` → `GetNewData`).
- Behaviour:
  1. The handler builds `DELETE FROM Persons WHERE AAID > 0` and passes that string to `GetPersonProfile`, which runs it with `ExecuteReader`, not `ExecuteNonQuery`. Whether the delete is applied is **Unknown / Requires Confirmation**. The comment says the intent is to clear applicants.
  2. GET `https://footprintapp.net/api/accreditation/getapplicants/ac/{AID}/id/{AASID}`. `{AASID}` is `0` when no person row is read for the event. Otherwise it is the `AASID` of the last row returned by `SELECT * FROM Persons WHERE AC = '{AID}'` with no `ORDER BY`.
  3. The body is parsed as JSON with a `data` array. `Person.save_to_db` skips an item whose `Barcode` is null or `""`. A dated comment (2019-07-18, maanie) states that rule.
  4. If `save_to_db` returns an `AASID` greater than 0, the download repeats (paging). Otherwise, or when `data` is empty, image download starts.
  5. A thrown error sets the label to "Unable to download data".
- Evidence: `Setup.Download_Data`, `Setup.GetNewData`, `Setup.DownloadStringCompleted`, `Person.save_to_db(string)`.
- Status: **Implemented**. Confidence: high for the request sequence. Confidence is low that a brand-new person row is inserted: the INSERT SQL in both `save_to_db` overloads puts a comma inside the FirstName literal. `ExecuteData` returns false and does not tell the operator. The matching UPDATE is well-formed. See technical documentation.

**FR-011 — Download one profile image at a time**

- Trigger: applicant paging finished, and the connectivity probe succeeds.
- Behaviour: select one person with `User_pic` null, empty, or `0` (`LIMIT 1`). GET `https://footprintapp.net/api/accreditation/getimages/ac/{AID}/id/{AAID}`. For each JSON item whose `User_pic` is not the string `NULL`, write the payload to `c:\temp_acc\{AAID}.txt` and set `User_pic` to `1`. Then request the next person. When none remain, show "Download Done" / "Download 100%" and open `Scan`.
- Evidence: `Setup.GetProfileImages`, `Setup.DownloadPicFileCompleted`, `Person.GetImagesUpdates`.
- Status: **Implemented**. Confidence: high.

**FR-012 — Import a CSV from a fixed path**

- Trigger: Import CSV.
- Input: `c:\temp_acc\accreditations.csv`. Missing file sets the caption to "File not found!".
- Behaviour: `TextFieldParser`, comma delimiter, fields may be quoted. There is no header skip. Each row is passed to `Person.save_to_db(string[])` in this order: AASID, AAID, AC, Ref, LastUpdate, FirstName, Surname, User_pic, Category, IDnumber, RoleColor, organisation, Role, Barcode, SCANID, aZones. The empty-barcode skip in the JSON overload is not applied here. On completion the body becomes `Scan`.
- Evidence: `Setup.Import_CSV`, `Person.save_to_db(string[])`.
- Status: **Implemented**. Confidence: high for the path and column order (the order is the array index, not a written header). Whether real files include a header row is **Unknown / Requires Confirmation**.

**FR-013 — Background refresh while the zone screen is online**

- Trigger: `WorkMode` is `ON` and `Scan` has started.
- Behaviour:
  - Every 120 seconds, if the connectivity probe succeeds and `DownloadingImg` is 0, download the next missing image (same image URL as FR-011). At `43bc412` this interval was 15 seconds.
  - Every 240 seconds, under the same conditions, start a background thread that calls `Webservices.CheckUpdates`. At `43bc412` this interval was 20 seconds. The 2019-08-16 commit is the change (`bb6e583`).
  - `CheckUpdates` reads `c:\temp_acc\LastUpdate.txt`. If the file is missing it writes `2018:09:29 08:00` (colons, not a `yyyy-MM-dd` date) and uses that value. GET `https://footprintapp.net/api/accreditation/getapplicantsupdates/ac/{AID}/date/{lastUpdated}`. A non-empty `data` array is passed to `Person.save_to_db`. The file is then overwritten with `DateTime.Now` formatted `yyyy-MM-dd HH:mm`, including when `data` is empty.
  - Going to settings stops both timers (comment dated 2019-08-07).
- Image download and the update thread are skipped while `DownloadingImg != 0`.
- Evidence: `Scan.xaml.cs` constructor, `Download_Image`, `Download_UpdateAsync`, `Webservices.CheckUpdates`.
- Status: **Implemented**. Confidence: high.

`Webservices.GetNewData`, `CheckUpdatesAsync`, and `SaveNewUpdates` are not called from any page. `GetNewData` would GET `.../getapplicants/ac/{AID}` (no `/id/`) and its completed handler deletes from `Persons` then deserializes the body as a list of `Person`. That is a different contract from FR-010. Status: **Implemented** code, no live caller. `Setup.Save_Data` contains the commented call `//online.GetNewData()`.

## Zone scan

**FR-014 — Read a barcode and decide access**

- Who: the operator on `Scan`.
- Trigger: Tab or Enter in `Scanning`. A short timer then calls `UserProfile` with the text and clears the buffer.
- Lookup: `Persons` where `Barcode` equals the text and `AC` equals the active event id. NFC lookup (`SCANID`, flag `nfc = 1`) exists. `NFCHandler.Init` in the constructor is commented out, so a tag is not read unless something else initialises the reader. The zone `UserProfile` also requires the page field `Barcode` to be non-empty, which the NFC callback does not set. A tag path that did run would still no-op while that field is empty.
- Person found (`AASID > 0`): show name, organisation, category, role. Role colour defaults to `#00981E` when `RoleColor` is empty. Photo from `c:\temp_acc\{AAID}.txt` when that file exists: split on comma and decode index 1 as base64. Otherwise the event logo, or a built-in JPEG if the event has no logo.
- Person missing: show Invalid / N/A and the fallback image. Access stays denied.
- Access, when a person is found:
  1. Strip `"`, `[`, and `]` from `aZones`.
  2. If the remaining text contains `312`, treat the person as full access. This is a substring test, so an id such as `1312` also matches. **Implemented**. The meaning of `312` is **Unknown / Requires Confirmation**.
  3. If any zone for the event is `Lock = 1`, `Active = 1`, and the current `HHmm` is inside `FromTime`..`ToTime` inclusive, the candidate zones are the person's zones that are locked and active. Otherwise the candidate zones are the person's zones that are active. Full access replaces that set with the unlocked active query.
  4. Keep a candidate whose `FromTime` and `ToTime` are both 0, or whose window contains the current `HHmm`.
  5. Any kept zone sets access on. A kept zone with `ZTID = 24` also sets all-access and keeps that zone's `Icon`. The meaning of `24` is **Unknown / Requires Confirmation**.
  6. All-access shows the ALL ACCESS panel. Otherwise the screen draws up to 15 zone cells (3 rows × 5). `Hidden = 1` zones contribute an icon instead of a number.
- Grant: green `#00981E`, text GRANTED, `SystemSounds.Asterisk`. `images\ok.wav` is loaded at startup and the line that would play it is commented out. Append to `c:\temp_acc\scan.txt`: `{AASID}|{yyyy-MM-dd}|{HHmm}|{FirstName Surname}|1|0|;`
- Deny: orange `#BF360C`, text DENIED, play `images\Bazz.wav`. Append `{AASID}|{yyyy-MM-dd}|{HHmm}|{FirstName Surname};` (fewer fields than the grant line).
- The `|1|0|` pair is not named in code. Reading the two integers as allowed/not-allowed flags is **Inferred**.
- A 10-second timer started at the beginning of `UserProfile` replaces the page with a new `Scan`, which clears the result.
- Exceptions open a message box with `error.ToString()`.
- Evidence: `Scan.xaml.cs` `ScanninAcc`, `Scaning`, `UserProfile`, `Allowed`, `NotAllowed`, `getZones`; `Database.GetLockedCount`.
- Status: **Implemented**. Confidence: high for the control flow. The business meaning of `312`, `ZTID` 24, and the log tokens needs confirmation.

`GetBarcodeLength` reads `Events.BarcodeLength` for the active event and has no caller. The scan does not check barcode length.

## Meal scan

**FR-015 — Grant one meal per person, day, and session**

- Who: the operator on `Scanmeal`.
- Trigger: same Tab or Enter path, unless the text starts with `UNDO`.
- Lookup uses the method argument (unlike the zone screen). `AASID` is not the found-test; the meal screen tests `AAID > 0`.
- The zone rules in FR-014 steps 1–5 run again. If none of them set access, the result is DENIED, the undo panel is hidden, and no meal row is written. Meal sounds are commented out; grant and deny are colour and text only. The uncalled `playSound_OK` / `playSound_No` methods look for `ok.wav` and `Bazz.wav` in the process current directory, not under `images\`.
- If access is on:
  1. Show the undo panel.
  2. Look up `Food` for this `AASID`, `Events.DayNumber`, and `Events.SessionNumer`.
  3. Always append a line to `c:\temp_acc\meal.txt`: `{AASID}|{date}|{HHmm}|{name}|1|0|{DayNumber}|{SessionNumer};`
  4. If that `Food` row exists (`AASID > 0` on the loaded object): DENIED, and draw one box per `MealsPerDay`, highlighting `SessionNumer`. No second log line.
  5. If it does not: GRANTED, insert `Food (AASID, Day, Season)` using `DayNumber` and `SessionNumer`, and append a second line ending `|0|1|{DayNumber}|{SessionNumer};`.
- `Database.GetFoodProfile` assigns `Season` from the `Day` column. The decision uses `AASID`, which is read from the correct column. The in-memory `Season` value is not what the SQL filtered on.
- The screen title is `MEALS (Day {DayNumber})` and lists meal numbers `1..MealsPerDay`.
- A 10-second timer resets the page, same as the zone screen.
- Evidence: `Scanmeal.xaml.cs` `ViewProfile`, constructor; `Meals.GetRow`; `Database.GetFoodProfile`.
- Status: **Implemented**. Confidence: high for the sequence. The meaning of the two log shapes is **Inferred**.

**FR-016 — Undo a meal from a barcode**

- Trigger: Tab or Enter and the text splits on `|` with the first field equal to `UNDO`.
- The commented QR builder used `UNDO|{AASID}|day|{DayNumber}|season|{SessionNumer}`. The live parser deletes `Food` where `AASID =` field 2, `Day =` field 4, `Season =` field 6 (1-based fields 1, 3, and 5). That matches the commented string.
- If `ExecuteData` returns true, the meal page is replaced with a fresh one. If the delete matches no row, the page stays.
- The QR image control is not filled. Nothing in the live UI prints the undo code. An operator can still type or wedge a string of that shape.
- Evidence: `Scanmeal.ScanninAcc`, commented block in `ViewProfile`.
- Status: **Implemented**. Confidence: high. Whether operators still use undo codes is **Unknown / Requires Confirmation**.

## Local database

**FR-017 — Create the SQLite file on first use**

- Trigger: any `new Database()`.
- Behaviour: create `c:\temp_acc\` if needed. If `c:\temp_acc\database.sqlite3` is missing, create it and create `Events`, then `Zones`, then `Persons`, then `Food`. If the file exists, no migration runs. A failure in the constructor is swallowed, which can leave `myConnection` unset.
- There is no login, no encryption, and no row-level security. SQL is concatenated from scanned text and from JSON fields.
- Evidence: `Database` constructor and `CreateTables_*`.
- Status: **Implemented**. Confidence: high.

## Error and empty behaviour (summary)

| Situation | What the code does | Evidence |
|---|---|---|
| No active event | Setup screen | `MainWindow.getbody` |
| Event API unreachable on settings | Label "SELECT EVENT"; list not loaded | `Setup.get_time` |
| Download throws | "Unable to download data" | `Setup.DownloadStringCompleted` |
| CSV missing | "File not found!" | `Setup.Import_CSV` |
| Unknown barcode | Invalid / N/A, DENIED | `Scan.UserProfile`, `Scanmeal.ViewProfile` |
| Repeat meal | DENIED, no new `Food` row | `Scanmeal.ViewProfile` |
| SQL or IO exception on scan | Message box (`Scan`) or message box (`Scanmeal.UserProfile`). Many other catches are empty | both scan pages |
| Zone scan log write when `scan.txt` is missing | `ReadAllText` throws; the message box path can show it. The header creates the file on header construct | `Scan.UserProfile`, `header` constructor |

## Integrations

See `technical-documentation.md`. The only external system called by the live screens is Footprint over anonymous HTTPS GET, plus the local filesystem under `c:\temp_acc\`.

## Constraints

- Windows, .NET Framework 4.6, WPF. Not a web app and not the Xamarin app in `maguseventsxamarin`.
- One active event per database file.
- Fixed paths under `c:\temp_acc\`. The drive letter is hard-coded.
- No automated test, no installer at tip (the ClickOnce output was removed in `bb6e583`), no CI.
- Network refresh on the zone screen only, and only for the lifetime of that page while `WorkMode` is `ON`.

## Out of scope for this repository

- Creating or editing accreditation applications. People are downloaded or imported.
- Uploading `scan.txt` or `meal.txt`. Both files are append-only on disk. `Scanmeal.Save_Report` builds a `Checkin` and is not called (the call sites are commented out).
- User management, payments, and the Footprint server itself.
