# PRD — scoringApp

Evidence date: 2026-10-04. Source: `git@bitbucket.org:overdrive/scoringapp.git`, `master` at `04b1a38` (2022-12-22, "Deploy V1.0.3 BEta").

Provenance labels: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown**.

No formal PRD existed in the repository or in `silicon_ark/scoringApp/` before this file. Requirements below were read from the WPF application `Live_Scoring_V1`. Event display names for most buttons come from the Footprint payload (`Events.CommName`), not from strings in this repo. Where this document names an event, the name is the one hardcoded in `Scoring.xaml.cs` or the one implied by a code comment.

## Product purpose

**Inferred.** Sideline Manager is a Windows application for recording a rugby match while it is in progress. The user signs in, downloads fixtures assigned to that user from Footprint, opens a match, records score events, cards, substitutions, injuries, and stoppages, and can upload the commentary back to Footprint.

**Implemented names:** window title `Sideline Manager` (`MainWindow.xaml`); installer product `Sideline Manager` version 1.0.3, manufacturer `Evolve`; assembly title `Live_Scoring_V1`.

A written purpose statement was not found. **Unknown / Requires Confirmation** whether the app is only for rugby union, or also for other codes that share the same event catalogue.

## Users and interactions

| Role | What the code does | Provenance |
|---|---|---|
| Signed-in Footprint user | Posts username and password to the login API. On success, stores the user locally with `LoggedIn = 1` and downloads fixtures for that `UserID` | **Implemented** |
| Returning user on the same PC | If a local `Users` row has `LoggedIn = 1`, the app skips login and opens the fixture list | **Implemented** |
| Match operator | Opens a fixture and records events, the clock, and commentary | **Inferred** from the scoring screen. The code does not name this role |
| Match officials | Referee, assistant referee 1, and TMO names are stored from `MATCHOFFICIALS` and shown as data. They are not login roles | **Implemented** as data |

There is no role table, permission check, or union-scoped filter in this client beyond sending `UserID` to the fixtures API. What the API returns for that user is **Unknown** here.

`IsLoggedIN()` always returns false and is unused by the shell. The shell uses `IsLoggedIN_AlReady()`.

## Major workflows

### 1. Start and login

Trigger: process start (`MainWindow.load`).

1. Read `Users` where `LoggedIn = 1`. If the database cannot be opened, `CreateDataBase()` runs.
2. If a user id is found, show the fixture list.
3. Otherwise show the login page. Both email and password fields must be non-empty. The button label changes to `Proccessing ...`.
4. POST `UserName` and `Password` to `https://qa.footprintapp.net/api/login/login`.
5. If the JSON `UserID` is greater than 0, insert or update `Users` (password stored as entered) and set `LoggedIn = 1`.
6. Download fixtures, then navigate to the fixture list. The download is asynchronous. Navigation does not wait for it to finish.

Failure: an exception from `WebClient` is not caught in `CheckOnline`. Empty fields do nothing. A response with `UserID` not greater than 0 leaves the button on `Proccessing ...`. There is no offline password check. `MD5Hash` exists on `Loginn` and is not called by login.

Provenance: **Implemented**.

### 2. Download fixtures

Trigger: after login, the refresh control on the fixture list (`Listfixturs.Download_data`), or the refresh control on the match screen (`Scoring.Download_data`, which first deletes all rows from `Teams`).

1. GET `https://qa.footprintapp.net/api/fixtures/getdetails/UserID/{UserID}`.
2. For each fixture object: clear `Fixture.Current`, then insert the fixture. If the insert throws, update the row with the same `FID`.
3. Upsert home and away lineup players for that fixture.
4. Only for the first fixture object in the array: upsert `EVENTS` and `SESSIONS` into `Events`.

Officials: `PosDescription` of `Referee`, `Assistant Referee 1`, and `TMO` are stored. Other official rows are ignored.

The production URL `https://footprintapp.net/api/fixtures/getdetails/...` is commented out.

Progress UI uses the download byte count. On completion the progress popup is hidden. If `Reload == 1` (fixture-list refresh), the fixture page is constructed again. The match-screen refresh does not set `Reload`, so it does not navigate away. It calls `GetFixture()` immediately, which can run before the async download completes.

Internet probe `CheckForInternetConnection` (Google `generate_204`) exists. The timer in `MainWindow` that would call it is commented out.

Provenance: **Implemented**.

### 3. Fixture list

The list groups fixtures by `FixtureDate` and shows kickoff time, home name, home full score, `VS`, away full score, away name, and venue (`Location`). Choosing a row opens `Scoring` for that `FID`.

Logout sets every `Users.LoggedIn` flag to 0 and returns to the login page. It does not delete the stored password. Exit calls `Environment.Exit(1)`.

A New Fixture button opens `DetailFixture`.

Provenance: **Implemented**.

### 4. Create a fixture on the device

The form collects half length, sanction time, blood-bin time, half-time length, HIA time, venue, date, two team names, extra time, extra-time half length, try, conversion, penalty, and dropped-goal point values, plus referee, two assistant referees, TMO, and timekeeper.

Save calls `Fixtures.SetOfflineFixture` and returns to the list. The INSERT names columns that `SqlDatabase.CreateDataBase` does not create (`Sanction_Time`, `BloodBin_Time`, `HalfTimeLength`, `Extra_Time`, `ExtraTimeHalfLength`, `Referee`, `AssistantReferee1`, `AssistantReferee2`, `TMO`, `Timekeeper`), and the column list omits a comma before `YellowCardTimer`. The value list also reads `Fixture["YellowCardTimer"]`, `Fixture["BloodTimer"]`, and `Fixture["HIATimer"]`, which the form dictionary never adds. The catch block does not retry. Provenance: **Implemented** UI, and **Implemented** SQL that does not match the created schema. Whether a pre-seeded `Footprint.db` has those columns was not inspected. **Unknown / Requires Confirmation.**

### 5. Run a match

Trigger: open a fixture. `GetFixture` reloads the fixture, recalculates `Scores` from commentary, and paints the scoreboard.

Events can be chosen when `Game_status` is greater than 0 and less than 10 (`HomeEvents_Clicked` / `AwayEvents_Clicked`). Status 0 shows `MATCH START`. Status 10 stops the clocks and shows no session button.

Session buttons and the status values they set:

| `Game_status` | Button shown | Next action |
|---|---|---|
| 0 | MATCH START | Opens the kickoff popup |
| 1 | 1H End, and Time OFF while the clock runs | End of first half increments status |
| 2 | 2H START | Kickoff popup again |
| 3 | 2H End | Increments status |
| 4 | ET 1H START and END GAME | Kickoff popup, or complete the match |
| 5 | ET 1H END and END GAME | |
| 6 | ET 2H START 2H and END GAME | |
| 7 | ET 2H END and END GAME | |
| 8 or 9 | END GAME (because status is greater than 3) | |
| 10 | none | Clocks turn red |

Choosing home or away on the kickoff popup increments `Game_status`, stores `Time_Started`, and opens that side's on-field player list. Which product concept that choice represents (kicking side versus receiving side) is **Unknown / Requires Confirmation**. The code then expects a session-start event (`EventID` 27, 29, 31, or 33 depending on the new status).

Time OFF sets `Game_Paused = 1`, stops playing time, and starts stoppage timers. A popup asks for a reason: Weather or Other, Substitution, TMO, Strategic Break, Foul Play, Clothing, H.I.A, Talking to AR, or Injury. The reason is stored as a commentary row named `Time off`. Time ON sets `Game_Paused = 0`, writes a `Play resumed` row whose comment is the stoppage duration, and applies substitution and injury actions that were queued while paused.

Abandon match (from the back popup) sets `Game_status = 10` and writes the completed-match event (id 30).

A reset handler (`Scoring.Border_MouseDown`) deletes all commentary for the fixture and sets duration, running time, and `Game_status` back to `00:00` / `0`. No confirmation dialog is in that method.

Provenance: **Implemented**.

### 6. Record an event

The event buttons are the `Events` rows with `Session != 1`, excluding CEID 56 and 57. If `Allow22MeterTry` is not 1, CEID 53 is also excluded.

After a scoring event, the user picks a player. `Player_clicked` groups behaviour by `Events.Groups` and `EventID`:

| Group / id | Behaviour in code |
|---|---|
| Group 1, event 2, 3, 4, 5, or 53 | Insert a commentary row |
| Group 2 | Insert a commentary row (comment in code: missed conversion) |
| Group 3 | Treat as a substitution and show players whose `CurrentPos` is greater than `Fixture.Code` |
| Group 4, event 11 | Yellow card. See business rules |
| Group 4, event 14 | Red card. Sets player `Location` to 4. Further clicks on that player return immediately |
| Group 4, event 45 | Empty handler. Comment says blue card |
| Event 6 | Confirmation popup, then a commentary row. Copy says penalty try |

`Score.SetScore` rebuilds totals from commentary whenever the fixture is reloaded. Point values are the fixture columns `Tries`, `Conversion`, `Penalties`, `Dropgoal`, `PenaltyTry`, and `Portry`, multiplied by the count of events 2, 3, 4, 5, 6, and 53. Half-time totals count only `Session == 0`. The columns named full-time count every session, including extra time.

Commentary rows can be edited (time, event, squad, player, reason) and deleted after a confirm popup. Deleting a substitution event swaps the two players' `CurrentPos` back. Deleting event 11 or 14 sets that player's `Location` to 0. Deleting event 12 sets `Location` to 3.

Upload serialises `Comments.GetCommentary(FID)` to JSON and POSTs it as the form field `comment` to `https://qa.footprintapp.net/api/fixtures/uploadcommentary/fid/{FID}`. The boolean result is ignored. No auth header is set. Provenance: **Implemented**.

### 7. Lineup tab

Builds one row per position from 1 through `Fixture.Code`. Home and away players are placed by `CurrentPos`. Each side shows front-row flag, name, and jersey. Provenance: **Implemented**. The field `Code` is **Inferred** to be the number of players on the field for that match (for example 15 or 7). The repository does not define the code list.

## Integrations

| System | Direction | Trigger | Data | Auth | Failure |
|---|---|---|---|---|---|
| `POST https://qa.footprintapp.net/api/login/login` | Client to Footprint | Login click | `UserName`, `Password` form body. Response JSON: `UserID`, `UnionID`, `FirstName`, `LastName`, `FullName`, `Email` | None beyond the password field. Password is then stored locally in clear text | Uncaught `WebClient` exception. `UserID` not > 0 does not show an error |
| `GET https://qa.footprintapp.net/api/fixtures/getdetails/UserID/{id}` | Footprint to client | After login, or refresh | Fixture, both lineups, `MATCHOFFICIALS`, and on the first object only `EVENTS` and `SESSIONS` | User id in the path. No header observed | Empty `catch` in `GetNewData` before the request is started. Failures inside the completed handler are not given a user message in `wb_DownloadStringCompleted` |
| `POST https://qa.footprintapp.net/api/fixtures/uploadcommentary/fid/{FID}` | Client to Footprint | Upload icon on the match screen | Form field `comment` = JSON array of commentary rows | None observed | `PostComment` returns false. The caller ignores it |
| `http://clients3.google.com/generate_204` | Probe | Not called from the live shell (timer commented out) | None | None | Returns false |
| SQLite file `%AppData%\Sideline Manager\Footprint.db` | Local | All match writes | See technical documentation | None | Open failure creates a new database |

Commented production host: `https://footprintapp.net` for login, fixture download, and commentary upload. Provenance: **Implemented** as comments, not as the running URLs.

## Constraints

- Windows desktop, .NET Framework 4.8, WPF. Not a web app.
- One local database per Windows user profile, under AppData.
- Scoring point values and the legal event list are supplied by Footprint per fixture, except for the hardcoded exclusions and the session clock.
- First-half warning colours in `dt_Tick` are hardcoded around 35, 40, and 45 minutes while status is 1. The second-half playing clock can be set from `Fixture.HalfTime` (API field `HtimeMin`), or 40:00 when that value is empty.
- Yellow, blood, and HIA timer lengths are `YellowCardTimer`, `BloodTimer`, and `HIATimer` from the fixture, converted from minutes to seconds.
- SQL is built by concatenating values. Names from the API are escaped only in some paths (`StageName` strips single quotes; some person names double them; lineup names strip `/` and `\`).
- The event catalogue is replaced from the first fixture in a download only. Later fixtures in the same payload do not refresh `Events`.
- No automated test project is in the solution.

## Requirements discovered from the implementation

| ID | Requirement | Provenance |
|---|---|---|
| FR-001 | The app shell is a borderless window titled Sideline Manager. If a local user is marked logged in, open the fixture list; otherwise open login | **Implemented** |
| FR-002 | Login requires a non-empty username and password and succeeds only when the QA login API returns `UserID` > 0 | **Implemented** |
| FR-003 | A successful login stores the user, including the password, and marks `LoggedIn = 1` | **Implemented** |
| FR-004 | After login the app downloads that user's fixture details from the QA fixtures API into SQLite | **Implemented** |
| FR-005 | The fixture list shows date, time, both teams, both full-time score figures, and venue, and opens the match screen for the chosen `FID` | **Implemented** |
| FR-006 | Logout clears the logged-in flag and returns to login | **Implemented** |
| FR-007 | The match screen shows commentary, scores, movements, stoppages, and lineup tabs | **Implemented** (`Scoring.xaml` tab labels) |
| FR-008 | Match phase is a `Game_status` integer from 0 through 10, with the session buttons listed above | **Implemented** |
| FR-009 | Time OFF pauses playing time, records a reason, and Time ON records `Play resumed` with the stoppage duration | **Implemented** |
| FR-010 | Score totals are recomputed from commentary using the fixture's point values | **Implemented** |
| FR-011 | A player with `Location` 4 (red card) cannot be selected for a further event | **Implemented** |
| FR-012 | When the player has no existing event-11 commentary, a yellow card sets `Location` 3 and shows a sin-bin timer. Otherwise the code records a red card with comment `Second Yellow Card` | **Implemented** |
| FR-013 | The user can upload the fixture's commentary JSON to the QA upload endpoint | **Implemented** |
| FR-014 | The user can edit a commentary row and can delete one after confirmation, with location or position rollback for card and substitution events | **Implemented** |
| FR-015 | Refresh on the match screen deletes all `Teams` rows, then starts a download | **Implemented** |

## Assumptions and conflicts

- **Inferred:** `Fixture.Code` is the on-field squad size. It is used as the last starting `CurrentPos` and as the lineup row count.
- **Conflict:** event 53 is excluded unless `Allow22MeterTry == 1`, and `Score` multiplies it by `Portry`. The click handler comment calls it a drop goal. Drop goal is also event 5, multiplied by `Dropgoal`.
- **Conflict:** `SessionStartActions` uses event 33 when status becomes 7 (extra-time second half start). `End_sessionActions` uses event 33 when status becomes 8 (extra-time second half end).
- **Conflict:** `GetSession` comments label status 0 as "1H Started", but status 0 shows the MATCH START button and status 1 is the running first half.
- **Implemented but not applied:** `GetBlueCards` counts event 55 and is never called from `SetScore`, so blue cards do not change the scoreboard totals.
- **Not a requirement that could be tested as success:** `DetailFixture` save, until the column mismatch is resolved. See workflow 4.

Nothing in the repository is marked as a future release. Empty methods (`Update_clock_time`, fixture-list `Upload_data`, `FP LiveScoring V1` handlers) are unfinished code, not a written plan.

## Out of scope for this document

- The Footprint server rules that decide which fixtures a `UserID` receives.
- Changing the QA host back to production.
- The contents of remote branches other than `master`.
