# Technical documentation — bitool_dotnet

Evidence date: 2026-10-19.

Default branch: `master` at `b958e154` (2023-11-15). Implementation branch: `develop` at `2223f18b` (2024-02-22). Paths below are on `develop` unless the section says `master`.

## Identity

| Item | Value |
|---|---|
| Solution | `sawis_bi_tool.sln` |
| Visual Studio | Version 17, `VisualStudioVersion = 17.7.34009.444` |
| Remote | `git@bitbucket.org:overdrive/bitool_dotnet.git` |
| HTTPS | `https://bitbucket.org/overdrive/bitool_dotnet.git` |
| Default branch | `master` |
| Sonar project key | `overdrive_bitool_dotnet_AYvU0vSvT-jCdK_QYjRZ` in `bitbucket-pipelines.yml` |
| HTML title | SAWIS Report |
| npm package | `SAWIS BI` `0.1.0`, private |

`sonar-project.properties` and `sonarqube_scan.bat` were named in the onboarding file list and are not in Git.

## Stack

| Concern | Choice | Evidence |
|---|---|---|
| API | ASP.NET Core, `net7.0` | `sawis_bi_tool.API.csproj` |
| Services | Class library, `net7.0` | `sawis_bi_tool.Services.csproj` |
| SPA host | ASP.NET Core React template, `net7.0`, SpaProxy | `sawis_bi_tool.react.csproj` |
| UI | React 18.2.0, `react-scripts` 5.0.1 | `ClientApp/package.json` |
| State | Zustand 4.4.3 | `filterStore.js` |
| HTTP from the browser | `fetch` | `filterStore.js` |
| Charts | ECharts 5.4.3 | `BIChart.jsx`, `package.json` |
| CSS | Bootstrap 5.2.3. Tailwind 3.3.3 is installed with `content: []` | `index.js`, `tailwind.config.js` |
| Excel | `xlsx` 0.20.0 from `https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz`, plus `xlsx-js-style` | `ClientApp/package.json`, `PivotTable.jsx` |
| Database driver | `MySqlConnector` 2.2.7 | API and services csproj |
| JSON in services | `Newtonsoft.Json` 13.0.3 | Controllers call `JsonConvert.SerializeObject` |
| OpenAPI | Swashbuckle 6.5.0, `Microsoft.AspNetCore.OpenApi` 7.0.12 | API csproj |
| Analytics | `ga-4-react`, id `G-1RFV9ZJG9K` | `ClientApp/src/index.js` |
| Tests | `react-scripts test` via `cross-env CI=true`. One test renders `App` | `App.test.js` |

`axios` is a ClientApp dependency and is not imported under `ClientApp/src`. `oidc-client` and `faker` are also unused there. `chartjs-plugin-datalabels` is declared on the solution root, the SPA project, and the ClientApp. No Chart.js import was found under `src`.

## Solution layout

```text
sawis_bi_tool.sln
  1. Web App / sawis_bi_tool.react          develop only
  2. API     / sawis_bi_tool.API            both branches
  3. BL      / sawis_bi_tool.Services       both branches
  4. DAL     / (solution folder, no project)
```

`master` uses classic C# project type GUIDs and nests only the API and the services project. `develop` uses SDK-style GUIDs for those two projects and adds the React project.

### API

`src/api/sawis_bi_tool.API/`

- `Program.cs` — host, CORS, MySQL connection factory, service registration, Swagger, HTTPS redirection, authorization middleware, controller mapping.
- `Controllers/ChartDataController.cs`
- `Controllers/FilterController.cs`
- `Controllers/TableDataController.cs`
- `Properties/PublishProfiles/FolderProfile.pubxml` — filesystem publish to `C:\_Source\SAWIS\src\api\sawis_bi_tool.API\release`.

`appsettings.json` and `launchSettings.json` are not in Git. `develop` `.gitignore` names `appsettings.Development.json` and `Properties/launchSettings.json`, and also ignores `*.json` with exceptions `!package.json` and `!data.json`. No `data.json` is in the tree. `git log --all -- "*appsettings*"` is empty.

### Services

`src/bl/sawis_bi_tool.Services/`

- `Models/FilterModel.cs`
- `Models/MapperDataModel.cs` — chart and table DTOs in one file
- `Services/ChartDataServices/`
- `Services/FilterServices/`
- `Services/TableDataServices/`

There is no repository class. Each method opens `MySqlConnection` with `GetConnectionString("SAWIS")`, builds a SQL string, and maps a reader.

### Web app

`src/web_app/sawis_bi_tool.react/`

- `Program.cs` — static files, MVC route, fallback to `index.html`. HSTS when not Development.
- `Controllers/WeatherForecastController.cs` — template sample.
- `ClientApp/` — Create React App.

SPA proxy URL in the csproj is `https://localhost:44487`. `ClientApp/.env.development` sets `PORT=44487` and `HTTPS=false`. `ClientApp/.env` sets `BROWSER=none`. `setupProxy.js` forwards only `/weatherforecast`, and only to the ASP.NET dev URL (port `34992` if `ASPNETCORE_HTTPS_PORT` and `ASPNETCORE_URLS` are unset).

## HTTP API

All three controllers use route prefix `api/controller`. Actions are distinct, so the prefix does not collide.

| Method | Path | Body | Service |
|---|---|---|---|
| GET | `/api/controller/getFilters` | none | `FilterService.GetFilters` |
| POST | `/api/controller/getChartData` | `FilterModel` | `GetChartData` |
| POST | `/api/controller/getTOP25Data` | `FilterModel` | `GetTOP25Data` |
| POST | `/api/controller/getCultivarChartData` | `FilterModel` | `GetCultivarChartData` |
| POST | `/api/controller/getYearOverYearComparison` | `FilterModel` | `GetYearOverYearComparison` |
| POST | `/api/controller/getTotalLitres` | `FilterModel` | `GetTotalLitres` |
| POST | `/api/controller/getExports` | `FilterModel` | `GetExports` |
| POST | `/api/controller/tables/getExportMarketPerProductTypeTableData` | `FilterModel` | table service |
| POST | `/api/controller/tables/getExportsTableData` | `FilterModel` | table service |
| POST | `/api/controller/tables/getCultivarTypeExportsTableData` | `FilterModel` | table service |
| POST | `/api/controller/tables/getExportMarketVolumeByProductTypeTableData` | `FilterModel` | table service |
| POST | `/api/controller/tables/getTOP25TableData` | `FilterModel` | table service |

The client calls `getTop25Data` (capital T in Top). ASP.NET Core routing is case-insensitive by default. That was not executed against a host.

`ChartDataController` stores `apiVersion = "1.0.1"` and does not return it. `FilterController` sets `VersionAPI = "1.0"` on the filter payload.

Each successful action returns `Ok` of a string already produced by `JsonConvert.SerializeObject`. Combined with the framework JSON formatter, the wire body may be a JSON string rather than a JSON object. **Requires Confirmation.** No custom output formatter is registered.

Failures return `BadRequest(ex.Message)`.

`FilterModel` JSON names from the client are camelCase (`year`, `month`, `whiteOrRed`, `engelseLandBeskrywing`, `packageType`, `descriptionForReports`). The C# properties are PascalCase. The default ASP.NET Core JSON options bind camelCase. Newtonsoft is used only after binding, to serialise the result.

## Data

No DDL is in the repository. Columns below are the ones the SQL text reads. Types other than what the C# conversions assume are **Unknown**.

### Fact table

Name in source: `export_data_qa`. Commented alternative: `export_data`.

| Column in SQL | Used for |
|---|---|
| `year`, `month` | Latest period, filter lists |
| `period` | `BETWEEN` bounds shaped as `yyyyMM`, and `ORDER BY period DESC` |
| `totale_liters` | Sums |
| `white_or_red` | Product type |
| `description_for_reports` | Cultivar / report description |
| `sawis_landnaam` | Country |
| `bottels_bulk` | `BULK` or `BOTTELS` |
| `BB`, `BO`, `BU` | Package flags compared to `'X'` |

### Lookups

| Table | Columns read | Predicate |
|---|---|---|
| `product_type_lookup` | `white_or_red`, `` `DESCRIPTION FOR REPORTS` ``, `prd_kde` | `prd_kde` in the 20 codes listed in `FilterService` |
| `country_group_lookup` | `engelse_land_beskrywing` | none in the active query. Commented: `bb = 'X'` |

Connection string key: `SAWIS`. The value is not in Git.

`MySqlConnection` is also registered as transient in `Program.cs` and is not injected into the services. Services construct their own connections.

Some commands set `CommandTimeout = 300` (`GetTotalLitresHelper`, and the export and cultivar table queries in `TableDataService`). `GetChartData`, `GetExports`, and `GetTOP25Data` do not set it. The driver default then applies.

## Auth and security

| Topic | What the code does |
|---|---|
| Authentication | Not registered |
| Authorisation | `UseAuthorization()` with no policies and no `[Authorize]` |
| CORS | Any origin, any header, any method |
| Secrets in Git | No connection string was found. `.gitignore` ignores `*.json`, which hides `appsettings*.json` if a developer creates one. `ClientApp/.env` and `.env.development` are committed and contain no credentials |
| SQL | Filter text is concatenated inside quotes. A value containing a quote changes the statement. This is how `ApplyFilters` is written |
| HTTPS | `UseHttpsRedirection` on the API and the SPA host. The ClientApp dev server sets `HTTPS=false` |
| Swagger | Development only |
| Committed build output | `develop` contains `bin/`, `obj/`, `.vs/`, and nested `release/` folders, including DLLs and a `web.config`. `master` does not |

The publish profile points at a local `C:\_Source\SAWIS\...` folder. That path is not a deployment environment description.

## Pipeline

`bitbucket-pipelines.yml` is the same shape on both branches.

- Image: `mcr.microsoft.com/dotnet/sdk:7.0`.
- Branches: patterns `{master}` and `{develop}`.
- Step name: SonarQube analysis.
- Installs OpenJDK 17 JRE, installs `dotnet-sonarscanner`, runs `dotnet sonarscanner begin` with the project key and `SONAR_TOKEN` / `SONAR_HOST_URL`, runs `dotnet build`, then `dotnet sonarscanner end`.
- Caches: `dotnetcore`, `~/.sonar`.

There is no test step, no image publish, and no deploy step.

`dotnet build` on `develop` builds the React project as well. The Debug target runs `npm install` when `ClientApp/node_modules` is missing. The publish target runs `npm install` and `npm run build`. A pipeline build uses the default configuration, which for `dotnet build` is Debug unless the file says otherwise. The pipeline does not pass `-c`. Whether the SPA npm target runs in that pipeline was not executed.

## Client behaviour that affects the API

- Eleven requests are fired in parallel after filters exist. `Home.js` also runs `getReportingData` whenever `filters` changes, including the initial population.
- Loading flags are per widget. One failed call does not cancel the others.
- Number display: `formatNumber` uses two decimal places and a K/M/B suffix. Labels use zero decimal places. Tables use `toLocaleString("fr-FR")`.
- `uppercaseWords` special-cases a string containing `u.s.a` to the label `U.S.A`.
- Service worker is explicitly unregistered.
- `BrowserRouter` basename is the `<base href>` (`%PUBLIC_URL%/`).

## `master` differences

| Item | `master` |
|---|---|
| Projects | API and services only |
| Controllers | None. csproj has an empty `Controllers` folder include |
| `MapperDataModel` | Empty class |
| `ChartDataService` | Constructor stores `IConfiguration`. No methods |
| `Program.cs` | No CORS. Swagger registration duplicated after `app` is built |
| React | Absent |
| `.gitignore` | Ignores `*.json` with no `package.json` exception |
| History | 6 commits. Merge base with `develop` is `037bae06` |

## Testing and local run

Documented commands, from the SPA csproj and `ClientApp/package.json`:

- API: `dotnet build` / run the `sawis_bi_tool.API` project. It will not query MySQL until a connection string named `SAWIS` is supplied outside Git.
- SPA: `npm start` in `ClientApp`, which runs `aspnetcore-https`, `aspnetcore-react`, deletes `build`, and starts the dev server on port 44487.
- Test: `npm test` runs the single render test with `CI=true`.

No PHPUnit, xUnit, or NUnit project is in the solution. This investigation did not build or run the solution.

## Related repositories

| Silicon Ark path | Relationship |
|---|---|
| `bitool_frontend/` | Separate Git repository. Same display name and overlapping route names. Its API base URL is an environment variable, not the host hard-coded here |
| `bi_tool_frontend_phase6/` | Documents another branch of `bitool_frontend` |
| `bi_tool_php/` | PHP import scripts and database name `sawis_bitool`. Not referenced by this solution |

Whether those systems share a database with connection string `SAWIS` is **Unknown / Requires Confirmation**.
