# Gap: Missing Features ## 1. Localization System (HIGH) ### Problem The old app supported runtime Spanish/English switching via `R.LoadLanguage()` which swapped WPF `ResourceDictionary` files (`StringResources.xaml` for Spanish, `StringResourcesEN.xaml` for English). The new app has `AppSettings.Language` ("ESP"/"ENG") persisted but NOT used — all UI strings are hardcoded in English. ### Implementation approach 1. Create `Resources/StringResources.xaml` (Spanish) and `Resources/StringResources.en.xaml` (English) with keyed `sys:String` entries 2. Reference strings in XAML via `{DynamicResource key}` (not `StaticResource` — dynamic allows runtime swap) 3. Add `LanguageService` or extension method that merges the correct dictionary into `Application.Current.Resources` 4. Wire language toggle to `AppSettings.Language` change 5. Default resource dictionary should be Spanish (the primary user base) ### Scope All user-facing text: labels, button text, dialog messages, status messages, report text, error messages. Roughly 200-300 strings. ## 2. User Credential Encryption (HIGH) ### Problem Users stored as plaintext `user:password` pairs in `config.xml`. Default is `admin:admin`. Old system used AES-256 (Rijndael CBC, PBKDF2 1000 iterations) via `Encrypter.cs`. ### Recommended approach Don't re-implement the old Rijndael scheme (it used obsolete primitives and weak iteration count). Instead: 1. **Hash passwords** with `Rfc2898DeriveBytes` using HMAC-SHA256, 600,000 iterations, random 16-byte salt 2. Store as `user:salt:hash` in config.xml 3. `ValidateUser()` computes hash of input password with stored salt and compares 4. Migration: on first load of old-format `user:password` entries, hash them and rewrite This is more secure than the old encrypted-but-reversible approach. Passwords become irreversible. ### Files to modify - `Models/BenchConfiguration.cs` — change `Users` property format - `Services/Impl/ConfigurationService.cs` — `ValidateUser()`, `GetUsers()`, `UpdateUsers()`, `SaveSettings()` ## 3. KlineIDs Auto-Mapping (MEDIUM) ### Problem Old system remembered K-Line ID → pump ID associations via `Settings.Default.KlineIDs` (comma-separated `klineID:pumpID` string). When a pump was connected and identified via K-Line, the system could auto-select it next time. New system has no equivalent. ### Implementation 1. Add `Dictionary KlineIdMap` to `AppSettings` 2. Persist as `kline1:pump1,kline2:pump2` in config.xml 3. In `KwpService` or `PumpIdentificationViewModel`: after successful K-Line read, save the mapping 4. On pump connection: look up K-Line ID in map, auto-select pump if found ## 4. Test Phase Timer/Countdown Display (MEDIUM) ### Problem During test execution, conditioning and measurement phases have timed durations (e.g., 10 sec conditioning, 30 sec measurement). The old system showed a visual countdown. The new system fires `VerboseMessage` events with text like "Conditioning: 8s remaining" but there's no dedicated countdown UI. ### Implementation - Add countdown properties to `TestDisplayViewModel`: `RemainingSeconds`, `PhaseProgress` (0-1), `PhaseName` - Fire `PhaseTimerTick` event from `BenchService` with remaining seconds - Display as a circular progress indicator or large countdown text in `TestDisplayView` ## 5. QOver Zero-Flow Safety Check (HIGH — Safety) ### Problem The old system had a safety check: if `QOver == 0` while RPM > 300 and oil pump is on, trigger emergency stop (leak/blockage detection). This is completely absent from the new codebase. ### Implementation Add to `BenchService` or the ViewModel's bench monitoring loop: ```csharp if (qOverValue == 0 && benchRpm > 300 && IsRelayOn(RelayNames.OilPump)) { EmergencyStop(); _log.Error(LogId, "QOver zero-flow safety triggered: oil flow blocked while motor running"); } ``` Debounce for 2-3 seconds to avoid false positives during startup transients. ## 6. Alarm Bit Recording During Tests (HIGH) ### Problem `PhaseDefinition` has `ErrorBits` list and `RecordErrorBit(int bit)` method, but no code in `BenchService` ever calls it. The old system tracked which alarm bits fired during each test phase for inclusion in the report. ### Implementation In `BenchService.MeasurePhaseAsync()`, subscribe to alarm parameter changes. When an alarm bit transitions to active during a measurement phase, call `phase.RecordErrorBit(bit)`. The error bits are already rendered in the PDF report (PdfService checks `phase.ErrorBits`). ## 7. Per-Sample Real-Time UI Callback (MEDIUM) ### Problem Old system fired `ITestParameterListener.OnValueUpdate` for every measurement sample, enabling live chart updates during test execution. New system silently collects samples with no per-sample event. ### Implementation Add an event to `IBenchService`: ```csharp event Action? MeasurementSampled; // paramName, value, timestamp ``` Fire in `MeasurePhaseAsync` after each sample. ViewModel subscribes to update live charts. ## 8. Pump Parameter Zeroing Between Phases (MEDIUM) ### Problem Old system called `StopPump()` between phases, which zeroed ME/FBKW/PreIn values. New system calls `SetRpm(0)` but does NOT zero pump injection parameters. This means the pump may continue injecting fuel between phases at the previous phase's setpoint. ### Implementation Add a `StopPumpParameters()` method to `BenchService` that sets ME, FBKW, and PreIn to 0 and sends the CAN frame. Call it at the end of each phase (currently line 776 in BenchService.cs — after `SetRpm(0)`). ## 9. PDF Report Observations Section (LOW) ### Problem Old report had an "Observaciones" free-text section at the bottom. New `IPdfService.GenerateReport()` has no observations parameter and no observations rendering. ### Implementation 1. Add `string? observations` parameter to `IPdfService.GenerateReport()` 2. Add a text section at the bottom of the PDF body in `PdfService` 3. Add an observations text box to `ReportDialog`/`ReportViewModel` ## 10. PDF Auto-Open After Generation (LOW) ### Problem Old code called `Process.Start(filePath)` to open the generated PDF. New code returns the path but doesn't open it. ### Implementation After `GenerateReport()` returns the path, call `Process.Start(new ProcessStartInfo(path) { UseShellExecute = true })` in the ViewModel.