feat: implement SavePump/SaveAlarms, fix config round-trip bugs, redesign PDF reports

Config system fixes:
- Implement SavePump() — full XML serialization with insert/update by pump ID
- Add CanBusParameter.ToPumpXml() for legacy P1-P6 pump param format
- Fix LastRotationDirection never loaded in LoadSettings()
- Add SaveAlarms() to ConfigurationService and IConfigurationService
- Remove dead fields AppSettings.Clients and AppSettings.PumpIds

PDF report redesign:
- Professional layout with charts, verdict badges, and tolerance bands
- Add ReportChartRenderer (SVG) and ReportTheme styling constants
- Embed default_logo.png as fallback report logo

Documentation:
- Add gap analysis docs (config validation, ford unlock, missing features)
- Update CLAUDE.md architecture, known gaps, and debt tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-15 15:21:22 +02:00
parent 4891eb6812
commit c617854c09
15 changed files with 1495 additions and 141 deletions

View File

@@ -0,0 +1,76 @@
# Gap: Configuration Validation & Migration
## Problem
The configuration system loads 7 XML files with no schema validation, no bounds checking, and silent error swallowing. Malformed files produce partial/default state with no user indication. There is no migration path from the old 32-CFG-file format.
## Critical Issues
### 1. SavePump() Stub
**File:** `Services/Impl/ConfigurationService.cs:191-194`
**Impact:** Pump definition edits (test phase enable/disable, parameter changes, new pumps) are lost on restart.
**Fix:** Implement full XML serialization mirroring the `LoadPump`/`ParsePumpElement` logic. Use `PumpDefinition.ToXml()`, `TestDefinition.ToXml()`, `PhaseDefinition.ToXml()`. Write to `pumps.xml` at the correct XPath location. Handle both insert (new pump) and update (existing pump) cases.
### 2. LastRotationDirection Never Loaded
**File:** `Services/Impl/ConfigurationService.cs``SaveSettings()` writes `<LastRotationDir>` but `LoadSettings()` never reads it.
**Fix:** Add `TryInt(r, "LastRotationDir", v => settings.LastRotationDirection = (short)v);` in `LoadSettings()`.
### 3. No SaveAlarms()
**File:** `Services/Impl/ConfigurationService.cs`
**Impact:** Alarm configuration changes cannot be persisted.
**Fix:** Add `SaveAlarms()` using `Alarm.ToXml()` for each alarm, write to `alarms.xml`.
### 4. SaveSettings() Side Effects
**File:** `Services/Impl/ConfigurationService.cs:101-102`
**Impact:** Calling `SaveSettings()` always also writes sensors.xml and clients.xml.
**Fix:** Split into `SaveSettings()`, `SaveSensors()`, `SaveClients()` called independently. Or document the coupling clearly.
## Bounds Validation Rules
Add these checks in the respective `FromXml`/`Load*` methods:
| Field | Location | Valid Range | Guard |
|-------|----------|-------------|-------|
| `byteh` / `bytel` | `CanBusParameter.FromXml`, `ParseParamElement` | 07 | Clamp or reject + log |
| `RefreshCanBusReadMs` | `LoadSettings` | >= 1 | Floor at 1 |
| `RefreshPumpParamsMs` | `LoadSettings` | >= 1 | Floor at 1 |
| `SecurityRpmLimit` | `LoadSettings` | 1005000 | Clamp |
| `MaxPressureBar` | `LoadSettings` | 1100 | Clamp |
| `PidP, PidI, PidD` | `LoadSettings` | >= 0 | Floor at 0 |
| `Relay.Bit` | `ParseRelayElement` | 063 | Reject + log |
| `SensorConfiguration.MaxVolt - MinVolt` | `GetValueFromRaw` | != 0 | Return 0 + log |
| P1P6 denominator | `GetTransformResult` | != 0 | Return 0 + log |
## Locale-Safe Parsing
All `double.Parse` / `int.Parse` calls in XML loading must use `CultureInfo.InvariantCulture`. Known violations:
- `SensorConfiguration.FromXml``double.Parse(v)` with no culture (line ~78-83)
- `CanBusParameter.ParseDecimal` — already uses `InvariantCulture` (correct)
- `ConfigurationService.TryDouble` — uses `double.TryParse` with `InvariantCulture` (correct)
## Config Versioning Approach
Add a `version` attribute to each XML root element:
```xml
<Config version="2">...</Config>
<Pumps version="1">...</Pumps>
```
On load, check version. If missing or old, run migration logic. Bump version after migration. This enables safe format evolution.
## Error Reporting Strategy
Replace all `catch { }` and `catch (Exception) { }` blocks with:
```csharp
catch (Exception ex)
{
_log.Warning(LogId, $"Skipped malformed element '{elementName}': {ex.Message}");
}
```
Files affected:
- `ConfigurationService.cs``TryInt`, `TryDouble`, `TryBool`, `TryString` helpers (lines 621-646)
- `SensorConfiguration.cs``TryParse` wrapper (line 100-103)
## Dead Fields
`AppSettings.Clients` and `AppSettings.PumpIds` are never populated by `LoadSettings()` — they are dead fields. Either remove them or wire up the loading. Current live data paths: `ConfigurationService._clients` and `GetPumpIds()`.
## Old CFG → XML Migration Strategy
See `gap-pump-data-migration.md` for the full field mapping. In summary:
1. Parse each CFG file using the pipe-delimited format documented in `old_source/HerlicScripts/Program.cs`
2. Map CAN IDs, P1-P6 formulas, status bit definitions, and EEPROM passwords to the pumps.xml and status.xml schemas
3. Run once as a migration tool, not as runtime logic