This commit is contained in:
2026-04-17 14:55:32 -04:00
commit bc3ac1d4c9
18017 changed files with 4371742 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
---
source_files:
- Common/DTS.Common.Import/DatabaseLocks/LockImportGroups.cs
- Common/DTS.Common.Import/DatabaseLocks/LockImportTestSetups.cs
- Common/DTS.Common.Import/DatabaseLocks/LockImportSensors.cs
generated_at: "2026-04-16T02:06:50.106613+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "95ed39e21f5416a3"
---
# Documentation: Database Locking for Import Operations
## 1. Purpose
This module provides database-level locking for import operations on three distinct entity types—**Groups**, **Test Setups**, and **Sensors**—to prevent concurrent modifications during data import workflows. Each class (`LockImportGroups`, `LockImportTestSetups`, `LockImportSensors`) implements the `ILockImport` interface and encapsulates logic to acquire, release, and (if authorized) steal locks on the respective entities. Locks are managed via the shared `LockManager` utility, with support for detecting and reclaiming *stranded locks* (i.e., locks held longer than a configurable timeout) and handling contention by requiring explicit admin consent. These classes exist to ensure data integrity and consistency during import processes that modify critical system resources.
## 2. Public Interface
All three classes implement the same interface `ILockImport`, with identical method signatures and semantics. Below are the concrete implementations per class.
### `LockImportGroups`
- **Constructor**:
`LockImportGroups(User currentUser, double strandedLockTimeoutMinutes)`
Initializes the instance with the current user and timeout (in minutes) after which a lock is considered stranded.
- **`bool Contended { get; }`**
Returns `true` if any group lock acquisition failed due to contention (i.e., another user holds the lock and it is not expired or owned by the current user).
- **`void FreeLock(ref ImportObject importObject)`**
Releases all locks currently held by this instance (stored in `_lockedGroups`). Iterates over `_lockedGroups` and calls `LockManager.FreeLock(...)` for each. No-op if `_lockedGroups` is empty.
- **`void SetLock(ref ImportObject importObject, ref StringBuilder message)`**
Attempts to acquire locks for all groups returned by `importObject.StaticGroups()`.
- Clears `_lockedGroups` and `_contendedGroups` first.
- For each group:
- Calls `LockManager.LockItem(...)` with category `LockManager.ItemCategories.Group`.
- On failure:
- Skips if `lockError.ErrorCode == LockError.ITEM_NOT_FOUND`.
- Otherwise, checks if lock is expired (`LastUpdated + timeout < Now`) or owned by current user/machine. If so, frees and re-acquires the lock, adding to `_lockedGroups`.
- Else, adds the existing lock record to `_contendedGroups`.
- On success, adds lock record to `_lockedGroups`.
- If any locks are contended, appends a user-facing message listing each contended group (key, owner, machine) using `StringResources.ImportTestSetup_GroupsLocked`.
- **`bool StealLock(bool proceed)`**
Attempts to steal contended locks (only if `proceed == true` and `_currentUser.IsAdmin == true`).
- Returns `true` if no contention or if stealing succeeded.
- Returns `false` if user is not admin or `proceed` is `false`.
- For each contended group:
- Calls `LockManager.FreeLock(...)` to release the existing lock.
- Calls `LockManager.LockItem(...)` again (note: category incorrectly set to `LockManager.ItemCategories.Sensor`—see *Gotchas*).
- Adds the new lock record to `_lockedGroups`.
### `LockImportTestSetups`
- **Constructor**:
`LockImportTestSetups(User currentUser, double strandedLockTimeoutMinutes)`
Same semantics as `LockImportGroups`.
- **`bool Contended { get; }`**
Same semantics.
- **`void FreeLock(ref ImportObject importObject)`**
Releases locks in `_lockedTests` via `LockManager.FreeLock(...)`.
- **`void SetLock(ref ImportObject importObject, ref StringBuilder message)`**
Attempts to lock all test setups returned by `importObject.TestSetups()`, using category `LockManager.ItemCategories.TestSetup`.
- On contention, appends message using `StringResources.ImportTestSetup_TestsLocked`.
- **`bool StealLock(bool proceed)`**
Same logic as `LockImportGroups.StealLock(...)`, but operates on `_contendedTests` and `_lockedTests`.
### `LockImportSensors`
- **Constructor**:
`LockImportSensors(User currentUser, double strandedLockTimeoutMinutes)`
Same semantics.
- **`bool Contended { get; }`**
Same semantics.
- **`void FreeLock(ref ImportObject importObject)`**
Releases locks in `_lockedSensors` via `LockManager.FreeLock(...)`.
- **`void SetLock(ref ImportObject importObject, ref StringBuilder message)`**
Attempts to lock all sensors referenced in `importObject.Sensors()`.
- Pre-builds a `sensorLookup` from `SensorsCollection.SensorsList.GetAllSensors(true)` keyed by `SerialNumber`.
- Skips sensors not in the lookup.
- Uses category `LockManager.ItemCategories.Sensor`.
- On contention, appends message using `StringResources.ImportTestSetup_TestsLocked` *(note: same string as test setups—see *Gotchas*)*.
- **`bool StealLock(bool proceed)`**
Same logic as others, but operates on `_contendedSensors` and `_lockedSensors`.
## 3. Invariants
- **Lock ownership and expiration**:
A lock is considered reclaimable if either:
- `existingLock.LastUpdated.AddMinutes(_strandedLockTimeoutMinutes) < DateTime.Now`, *or*
- `existingLock.LockingUserName == _currentUser.UserName && existingLock.LockingMachineName == Environment.MachineName`.
- **Lock state separation**:
After `SetLock(...)` completes, `_lockedGroups`/`_lockedTests`/`_lockedSensors` contains *only* locks successfully acquired (including re-claimed ones), and `_contendedGroups`/`_contendedTests`/`_contendedSensors` contains *only* locks that could not be acquired due to active contention.
- **`Contended` property**:
Always reflects whether `_contended*` list is non-empty *after* the last `SetLock(...)` call.
- **Admin requirement for stealing**:
`StealLock(...)` returns `false` unless `_currentUser.IsAdmin == true` *and* `proceed == true`.
- **No double-locking**:
`SetLock(...)` clears the internal lists before processing, ensuring no stale lock records persist across calls.
## 4. Dependencies
### External Dependencies
- **`DTS.Common.Classes.Locking.LockManager`**: Central lock manager used for `LockItem`, `FreeLock`. Defines `LockError`, `LockRecord`, and `ItemCategories`.
- **`DTS.Slice.Users.User`**: Represents the current user; accessed for `UserName`, `Id`, and `IsAdmin`.
- **`DTS.Common.Storage.LockManager`**: Used in `LockImportSensors` to access `SensorsCollection.SensorsList.GetAllSensors(...)`.
- **`DTS.Common.SharedResource.Strings.StringResources`**: Provides localized messages (e.g., `ImportTestSetup_GroupsLocked`, `ImportTestSetup_TestsLocked`).
- **`DTS.Common.Utilities.Logging.APILogger`**: Used to log exceptions during lock operations.
### Interface Dependencies
- **`ILockImport`**: All three classes implement this interface (not shown in source, but implied by usage and naming). Signature must include `SetLock`, `FreeLock`, `StealLock`, and `Contended`.
### Depended Upon
- These classes are likely used by higher-level import orchestrators (e.g., `ImportService`, `ImportWizard`) that coordinate import workflows and handle user prompts for lock stealing.
## 5. Gotchas
- **Incorrect category in `StealLock` for Groups**:
In `LockImportGroups.StealLock(...)`, the re-lock call uses `LockManager.ItemCategories.Sensor` instead of `LockManager.ItemCategories.Group`. This is inconsistent with the initial lock and may cause lock metadata corruption or misattribution.
- **Incorrect resource string for sensor contention messages**:
In `LockImportSensors.SetLock(...)`, the message appended on contention uses `StringResources.ImportTestSetup_TestsLocked` (same as test setups), not a sensor-specific string. This may mislead users.
- **No validation of `ImportObject` contents**:
The classes assume `importObject.StaticGroups()`, `TestSetups()`, and `Sensors()` return valid collections. No null/empty checks are performed on the collections themselves.
- **Exception swallowing in `SetLock`**:
All exceptions during lock acquisition are caught and logged via `APILogger.Log(ex)`, but the method continues silently. This may hide critical failures (e.g., database connectivity issues) and leave the system in an inconsistent state (e.g., partial locking).
- **Race condition in lock stealing**:
Between `FreeLock` and `LockItem` in `StealLock(...)`, another user could acquire the lock. The code does not re-check lock ownership before re-locking.
- **Ambiguity in `ImportObject` interface**:
The source does not define `ImportObject` or its methods (`StaticGroups`, `TestSetups`, `Sensors`). Their behavior (e.g., ordering, nullability) is unknown.
- **No rollback on partial failure**:
If `SetLock(...)` fails partway through (e.g., due to an exception), already-locked items are *not* released. Only `FreeLock(...)` (called later) cleans up.
- **Machine name dependency**:
Lock ownership check uses `Environment.MachineName`, which may be unreliable in containerized/distributed environments or if machine names change.
None identified beyond the above.

View File

@@ -0,0 +1,110 @@
---
source_files:
- Common/DTS.Common.Import/Factories/CSVTestParserFactory.cs
- Common/DTS.Common.Import/Factories/DatabaseLocksFactory.cs
- Common/DTS.Common.Import/Factories/CSVSensorParserFactory.cs
- Common/DTS.Common.Import/Factories/SaveVariantFactory.cs
- Common/DTS.Common.Import/Factories/XmlParserFactory.cs
generated_at: "2026-04-16T02:07:25.656251+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "23d838f43f91b04f"
---
# Import Factories Module Documentation
## 1. Purpose
This module provides factory classes responsible for constructing and configuring domain-specific parsers and persistence handlers used during data import operations. It centralizes the logic for instantiating versioned CSV and XML parsers (for tests, sensors), database lock objects, and save handlers for various import entities (e.g., sensors, groups, test setups). Its role is to decouple object creation from consumption, enabling version-aware and context-aware instantiation of import components based on input data format, version, and configuration options.
## 2. Public Interface
### `CSVTestParserFactory.CreateCSVParsers()`
**Signature:** `public static IParseCSVTest[] CreateCSVParsers()`
**Behavior:** Returns an array of versioned CSV test parsers (`Version0CSVTestParser`, `Version5CSVTestParser`, `Version6CSVTestParser`) implementing `IParseCSVTest`. Parsers are returned in version order (v0, v5, v6). No initialization parameters are required.
### `DatabaseLocksFactory.Create(ImportObject, User, double)`
**Signature:** `public static List<ILockImport> Create(ImportObject importObject, User user, double strandedLockTimeoutMinutes)`
**Behavior:** Returns a list of database lock objects implementing `ILockImport`, conditionally instantiated based on the presence of data in the `ImportObject`. Lock types created include:
- `LockImportTestSetups` (if `importObject.TestSetups()` is non-empty)
- `LockImportSensors` (if `importObject.Sensors()` is non-empty)
- `LockImportGroups` (if `importObject.StaticGroups()` is non-empty)
Each lock is initialized with the provided `user` and `strandedLockTimeoutMinutes`.
### `CSVSensorParserFactory.CreateCSVParsers(...)`
**Signature:**
```csharp
public static IReadOnlyDictionary<int, IParseCSVSensor> CreateCSVParsers(
ICalibrationImport import,
ZeroMethodOptions zmOptions,
IImportNotification importNotification,
bool importCreateDynamicGroups,
bool useISOCodeFilterMapping,
bool useZeroForUnfiltered)
```
**Behavior:** Returns a dictionary mapping parser version numbers (int) to initialized `IParseCSVSensor` implementations (`Version0CSVSensorParser`, `Version2CSVSensorParser`, `Version3CSVSensorParser`, `Version4CSVSensorParser`). Each parser is initialized via its `Initialize(...)` method with the provided parameters. Keys are the parsers `Version` property values.
### `SaveVariantFactory.CreateVariants(...)`
**Signature:**
```csharp
public static List<IPersistImport> CreateVariants(
ImportObject importObject,
ImportNotification importNotification,
User user,
Func<bool> isCanceled,
bool showCheckoutButton)
```
**Behavior:** Returns a list of `IPersistImport` implementations (save handlers) for various import entities, conditionally instantiated based on content in `importObject`. Includes:
- `SaveCustomerDetails`, `SaveTestEngineerDetails`, `SaveLabDetails`, `SaveSensorModels`, `SaveUsers`, `SaveGlobalSettings`
- `SaveCsvSourceSensor` or `SaveNonCsvSourceSensor` (based on `importObject.SourceFormat == ImportFormats.DTS_CSV`)
- `SaveHardware`, `SaveGroupTemplates`, `SaveGroups`, `SaveCustomChannels`
- `SaveTestSetup` and optionally `SaveCheckoutTestSetup` (if `importObject.TestSetups()` is non-empty and `showCheckoutButton` is true)
The `SaveTestSetup` name is modified by appending `Serialization.RDF.File.SUFFIX_RUNTEST`. If `showCheckoutButton` is true, a second `SaveCheckoutTestSetup` is created with `SUFFIX_CHECKOUT`. All handlers receive `isCanceled` and `importNotification`, and some receive `user` or other dependencies (e.g., `saveHardware`, `saveGroups`). A shared `PersistCalculator` instance is used to track progress totals.
### `XmlParserFactory.CreateXMLParsers(...)`
**Signature:**
```csharp
public static IEnumerable<IParseVariant> CreateXMLParsers(
string fileName,
IImportNotification importNotification,
Func<bool> isCanceled,
bool skipNormalizing)
```
**Behavior:** Returns an enumeration of `IParseVariant` implementations based on the XML file version (determined via `FileUtils.GetImportXmlNode(...)`).
- For versions **< `FileUtils.DataPRO20XmlVersion`**: Calls `LessThan20XMLVersion(...)`, wrapping parsers in pre-20-specific adapters (e.g., `XMLPre20ParseSensors`, `XMLPre20ParseCalibrations`).
- For versions **≥ `FileUtils.DataPRO20XmlVersion`**: Calls `GreaterOrEqual20XMLVersion(...)`, using direct parsers (e.g., `XMLParseSensors`, `XMLParseCalibrations`).
Supported `TopLevelFields` include: `CustomChannels`, `CustomMainLocs`, `DASList`, `Sensors`, `Calibrations`, `GroupTemplates`, `Groups`, `TestSetups`, `LabDetails`, `CustomerDetails`, `TestEngineerDetails`, `Users`.
Parsers are initialized with `importNotification`, `isCanceled`, and version-specific parameters. Static properties `UIItems` and `ImportNotification` are set before parsing.
## 3. Invariants
- **Parser versioning**: CSV parsers are versioned and returned in ascending order (e.g., v0, v5, v6 for tests; v0, v2, v3, v4 for sensors). XML parsers are selected based on a hard-coded version threshold (`FileUtils.DataPRO20XmlVersion`).
- **Conditional instantiation**: All factories instantiate components only when corresponding data exists in `ImportObject` (e.g., `TestSetups().Any()`, `Sensors().Any()`), avoiding unnecessary object creation.
- **Initialization consistency**: For `CSVSensorParserFactory`, all parsers are initialized with identical parameters and must be fully initialized before use (via `Initialize(...)`).
- **Progress tracking**: `SaveVariantFactory` uses a shared `PersistCalculator` instance to accumulate total item counts across all save handlers.
- **Checkout behavior**: `SaveCheckoutTestSetup` is created *only* if `showCheckoutButton` is true and `TestSetups()` is non-empty. The test setup name is modified using `Serialization.RDF.File.SUFFIX_RUNTEST`/`SUFFIX_CHECKOUT`.
## 4. Dependencies
### This module depends on:
- `DTS.Common.Import.Interfaces` (`IParseCSVTest`, `IParseCSVSensor`, `ILockImport`, `IPersistImport`, `IParseVariant`, `IImportNotification`, `ICalibrationImport`)
- `DTS.Slice.Users` (`User`)
- `DTS.Common.Import.DatabaseLocks` (`LockImportTestSetups`, `LockImportSensors`, `LockImportGroups`)
- `DTS.Common.Import.Parsers.CSV` (`Version0CSVTestParser`, `Version5CSVTestParser`, `Version6CSVTestParser`, `Version0CSVSensorParser`, `Version2CSVSensorParser`, `Version3CSVSensorParser`, `Version4CSVSensorParser`)
- `DTS.Common.Import.Persist` (`SaveCustomerDetails`, `SaveTestEngineerDetails`, `SaveLabDetails`, `SaveSensorModels`, `SaveCsvSourceSensor`, `SaveNonCsvSourceSensor`, `SaveUsers`, `SaveGlobalSettings`, `SaveHardware`, `SaveGroupTemplates`, `SaveGroups`, `SaveCustomChannels`, `SaveTestSetup`, `SaveCheckoutTestSetup`, `PersistCalculator`)
- `DTS.Common.Import.XML` (XML parser types, e.g., `XMLParseSensors`, `XMLPre20ParseSensors`)
- `DTS.Common.Enums.DBExport` (`TopLevelFields`)
- `DTS.Common.Utils` (`FileUtils`, `Serialization.RDF.File`)
- `System.Xml` (`XmlElement`, `XmlDocument`)
### This module is depended upon by:
- Import orchestration logic (e.g., `ImportService`, `ImportProcessor`) that consumes these factories to build import pipelines.
*(Exact callers are not visible in the provided source but are implied by the factory pattern usage.)*
## 5. Gotchas
- **Static state in `XmlParserFactory`**: The factory uses static properties (`UIItems`, `ImportNotification`) and a static field (`_isCancelled`) to pass context into parsers. This introduces thread-safety concerns if `CreateXMLParsers` is called concurrently.
- **`SaveVariantFactory` mutates `SaveCsvSourceSensor`/`SaveNonCsvSourceSensor`**: The `CurrentUser` property is assigned *after* construction, not via constructor. Callers must ensure this is set before use.
- **`SaveTestSetup.TestSetupName` modification**: The test setup name is modified *only* when `showCheckoutButton` is true, appending `SUFFIX_RUNTEST`. This behavior is tied to GM parse mode (per comment `//FB 38039`), but the condition is `showCheckoutButton`, not a dedicated flag.
- **XML version threshold**: The version split (`< 20` vs `>= 20`) is based on `FileUtils.DataPRO20XmlVersion`, but the constant itself is not defined in the provided source—its value must be verified externally.
- **`LessThan20XMLVersion` early exit**: Returns an empty list if `node == null || node.ChildNodes == null`, but does not log or signal an error. Callers must handle empty results gracefully.
- **`SaveGroups` and `SaveTestSetup` dependencies**: `SaveGroups` and `SaveTestSetup` constructors require `saveHardware` and `saveGroups` (respectively) as parameters, but these are instantiated *after* their dependencies in the factory method. This implies the factory method must construct them in dependency order (e.g., `saveHardware` before `saveGroups`, `saveGroups` before `saveTestSetup`).

View File

@@ -0,0 +1,69 @@
---
source_files:
- Common/DTS.Common.Import/ImportOptions/EqxImportOptions.cs
- Common/DTS.Common.Import/ImportOptions/CsvImportOptions.cs
generated_at: "2026-04-16T02:06:34.717084+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "c231715b4e36a96d"
---
# ImportOptions
## Documentation: Import Options Module
### 1. Purpose
This module defines data structures for configuring import behavior in the DTS system, specifically for equipment (Eqx) and CSV-based sensor data ingestion. It centralizes user-controllable settings that dictate how imported data interacts with existing system state (e.g., overwriting sensors, importing sensor models) and how raw data files are interpreted (e.g., encoding, separators, cultural formatting, zeroing logic). The classes reside in the `DTS.Common.Import.ImportOptions` namespace and serve as configuration payloads passed to import pipelines.
### 2. Public Interface
#### `EqxImportOptions`
- **Namespace**: `DTS.Common.Import.ImportOptions`
- **Properties**:
- `bool OverwriteExistingSensors { get; set; } = true`
Controls whether imported equipment sensor definitions should replace existing sensors with matching identifiers. Defaults to `true`.
- `bool ImportSensorModels { get; set; } = true`
Determines whether sensor model metadata (e.g., calibration curves, physical specs) should be imported alongside sensor instances. Defaults to `true`.
#### `CsvImportOptions`
- **Namespace**: `DTS.Common.Import.ImportOptions`
- **Properties**:
- `string Encoding { get; set; }`
Specifies the text encoding used in the CSV file (e.g., `"utf-8"`, `"iso-8859-1"`). No default is set; must be provided by caller if non-default encoding is required.
- `string FieldSeparator { get; set; }`
Defines the character(s) used to delimit fields in the CSV (e.g., `","`, `";"`, `"\t"`). No default is set; must be provided by caller.
- `CultureInfo ImportCulture { get; set; }`
Specifies the culture used to parse numeric values (e.g., decimal separators, date formats). No default is set; must be provided by caller.
- `bool StripBackSlash { get; set; }`
Indicates whether leading/trailing backslashes (`\`) should be removed from field values during parsing. No default is set; defaults to `false` (C# default for `bool`).
#### `ZeroMethodOptions`
- **Namespace**: `DTS.Common.Import.ImportOptions`
- **Properties**:
- `ZeroMethodType ZeroMethodType { get; set; }`
Specifies the algorithm used to compute a zero offset (e.g., `Average`, `Median`, `Manual`). Type is defined in `DTS.Common.Enums.Sensors`.
- `double ZeroMethodStart { get; set; }`
Start index or time offset (in seconds or samples, depending on context) for the region used to calculate the zero offset.
- `double ZeroMethodEnd { get; set; }`
End index or time offset for the zero-calculation region. Must be ≥ `ZeroMethodStart`.
### 3. Invariants
- For `ZeroMethodOptions`: `ZeroMethodEnd ≥ ZeroMethodStart` must hold. Violation implies invalid configuration, though no runtime enforcement is visible in this file.
- `CsvImportOptions` properties (`Encoding`, `FieldSeparator`, `ImportCulture`) are nullable reference types (no `= null!` or default initialization), implying they are *optional* but may be required by consumers.
- `OverwriteExistingSensors` and `ImportSensorModels` in `EqxImportOptions` default to `true`; callers relying on default behavior will overwrite and import models unless explicitly disabled.
- `StripBackSlash` defaults to `false` (C# default for `bool`), meaning backslash stripping is opt-in.
### 4. Dependencies
- **Imports/References**:
- `System` (core types, LINQ, `CultureInfo`)
- `DTS.Common.Enums.Sensors` (for `ZeroMethodType` enum used in `ZeroMethodOptions`)
- **Consumers**:
- Import pipeline classes (e.g., `EqxImporter`, `CsvImporter`) in the `DTS.Common.Import` hierarchy are implied consumers, though not visible here.
- UI or configuration layers that serialize/deserialize these options for user input.
### 5. Gotchas
- **Missing validation**: No validation is performed in these classes (e.g., `ZeroMethodEnd ≥ ZeroMethodStart`, non-empty `FieldSeparator`). Consumers must enforce constraints.
- **Ambiguous defaults**: `Encoding`, `FieldSeparator`, and `ImportCulture` in `CsvImportOptions` have no defaults. If left `null`, behavior depends on downstream parsing logic (e.g., `Encoding` may default to UTF-8 via `StreamReader`, but this is not guaranteed here).
- **`ZeroMethodOptions` semantics**: The unit of `ZeroMethodStart`/`ZeroMethodEnd` (samples vs. seconds) is context-dependent and not encoded in the type. Consumers must interpret consistently.
- **No inheritance or interfaces**: These are plain POCOs; no polymorphic behavior or shared base is defined.
- **No XML comments**: Source lacks documentation comments; all descriptions are inferred from property names and types.

View File

@@ -0,0 +1,92 @@
---
source_files:
- Common/DTS.Common.Import/Interfaces/IPersistImport.cs
- Common/DTS.Common.Import/Interfaces/IParseImport.cs
- Common/DTS.Common.Import/Interfaces/IParseVariant.cs
- Common/DTS.Common.Import/Interfaces/IParseCSVTest.cs
- Common/DTS.Common.Import/Interfaces/IParseCSVSensor.cs
- Common/DTS.Common.Import/Interfaces/ICalibrationImport.cs
- Common/DTS.Common.Import/Interfaces/ILockImport.cs
- Common/DTS.Common.Import/Interfaces/IGroupImport.cs
generated_at: "2026-04-16T02:07:53.745184+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "4d7a3572ba82cd30"
---
# Interfaces
## Documentation: `DTS.Common.Import` Module
---
### 1. Purpose
This module provides a structured, interface-driven abstraction layer for importing and persisting test and sensor configuration data—specifically supporting CSV-based import workflows. It defines contracts for parsing raw import files into a canonical `ImportObject`, handling variant-specific parsing logic, managing calibration and group creation, and enforcing database-level locking during import operations. Its role is to decouple import *logic* (e.g., CSV parsing, calibration application, group assignment) from *implementation* (e.g., concrete CSV readers, database access), enabling versioned, testable, and maintainable import pipelines.
---
### 2. Public Interface
| Interface | Method/Property | Signature | Behavior |
|-----------|-----------------|-----------|----------|
| **`IPersistImport`** | `Save` | `void Save();` | Persists the current state of the `ImportObject` (or associated data) to persistent storage (e.g., database). No parameters; no return value. |
| **`IParseImport`** | `Parse` | `ImportObject Parse(IEnumerable<string> importFiles);` | Parses one or more import files (e.g., CSVs) and returns a fully populated `ImportObject`. Input is a collection of file paths/URIs. |
| **`IParseVariant`** | `FileName` | `string FileName { get; set; }` | Gets or sets the name of the file this variant parser is responsible for. |
| | `Parse` | `void Parse(ref ImportObject importObject);` | Modifies the provided `ImportObject` in-place using data specific to this variant (e.g., test setup, sensor metadata). |
| **`IParseCSVTest`** | `Version` | `int Version { get; }` | Gets the version number this parser supports (e.g., for versioned CSV formats). |
| | `ParseVersion` | `void ParseVersion(CsvReader csvReader, TestSetupImportData tsid);` | Parses version-specific test setup data from a `CsvReader` into the provided `TestSetupImportData` instance. |
| **`IParseCSVSensor`** | `Version` | `int Version { get; }` | Gets the version number this sensor parser supports. |
| | `Initialize` | `void Initialize(ICalibrationImport import, ZeroMethodOptions zmOptions, IImportNotification importNotification, bool importCreateDynamicGroups, bool useIsoCodeFilterMapping, bool useZeroForUnfiltered);` | Configures the parser with dependencies and options required for sensor parsing (e.g., calibration, zero method, group creation behavior). |
| | `ParseVersion` | `void ParseVersion(CSVImportTags.Tags field, string val, ParseParameters pp);` | Parses a single field (`field`) and its value (`val`) into the `ParseParameters` context. Used for incremental sensor data parsing. |
| **`ICalibrationImport`** | `AddLinearCalRecordIfNeeded` | `SensorCalibration AddLinearCalRecordIfNeeded(SensorCalibration sc, bool savedIsProportional, bool savedRemoveOffset);` | Conditionally adds a linear calibration record to `sc` based on `savedIsProportional` and `savedRemoveOffset`. Returns the (possibly modified) `SensorCalibration`. |
| | `AddLinearZeroMethodIfNeeded` | `SensorCalibration AddLinearZeroMethodIfNeeded(SensorCalibration sc, ZeroMethodType zeroMethodType, double zeroMethodStart, double zeroMethodEnd);` | Conditionally adds a linear zero method (e.g., offset, span) to `sc`. Returns the (possibly modified) `SensorCalibration`. |
| | `CheckForExcitationCalibration` | `SensorCalibration CheckForExcitationCalibration(SensorCalibration sc, double sensitivity, ExcitationVoltageOptions.ExcitationVoltageOption excitation, string EU);` | Applies excitation-specific calibration adjustments to `sc` if applicable. Returns the (possibly modified) `SensorCalibration`. |
| **`ILockImport`** | `Contended` | `bool Contended { get; }` | Indicates whether the lock collection contains any items (i.e., whether contention exists). |
| | `SetLock` | `void SetLock(ref ImportObject importObject, ref StringBuilder message);` | Attempts to acquire a database lock for the given `importObject`. Errors (if any) are appended to `message`. |
| | `FreeLock` | `void FreeLock(ref ImportObject importObject);` | Releases the database lock previously acquired for `importObject`. |
| | `StealLock` | `bool StealLock(bool proceed);` | Attempts to forcibly acquire (steal) the lock. Returns `true` on success. `proceed` likely controls whether to proceed despite warnings. |
| **`IGroupImport`** | `ParseParameters` | `ParseParameters ParseParameters { get; set; }` | Gets or sets the shared parsing context used during group creation. |
| | `CreateGroups` | `Tuple<TestTemplate, List<IGroup>> CreateGroups(List<SensorData> sensors, Dictionary<string, List<TsetSetupImportSensorInfo>> groupSensorLookup, TestTemplate testTemplate, bool createDynamicGroups, List<IGroup> staticGroups, Action<double> setProgress);` | Creates groups (static and/or dynamic) from sensor data. Returns a tuple of the updated `TestTemplate` and list of created `IGroup` instances. `setProgress` enables progress reporting. |
| | `GetGroupSensorLookup` | `Dictionary<string, List<TsetSetupImportSensorInfo>> GetGroupSensorLookup(List<SensorData> sensors, Dictionary<string, string> sensorGroupNameLookup, Dictionary<string, List<string>> groupNameSensorListLookup);` | Builds a mapping from group names to their associated sensor info (`TsetSetupImportSensorInfo`) using precomputed lookup dictionaries. |
---
### 3. Invariants
- **`ImportObject` is the canonical data carrier**: All parsing interfaces (`IParseImport`, `IParseVariant`, `IParseCSVTest`, `IParseCSVSensor`) operate on or modify an `ImportObject` (or its subcomponents like `TestSetupImportData`). This object must be fully initialized before `IPersistImport.Save()` is called.
- **Versioned parsing is mandatory**: All CSV-specific parsers (`IParseCSVTest`, `IParseCSVSensor`) expose a `Version` property, implying that parsing logic must be version-aware and only process data matching its declared version.
- **`ParseParameters` is shared context**: `IGroupImport.ParseParameters` must be set consistently across all parsing steps that influence group creation (e.g., by `IParseCSVSensor.Initialize` or prior parsing steps).
- **Locking is explicit and paired**: `ILockImport.SetLock` and `ILockImport.FreeLock` must be called in matched pairs (or `StealLock` used appropriately) to avoid deadlocks or orphaned locks. The `message` parameter in `SetLock` is *mutated* and must be checked after each call.
- **Calibration is applied incrementally**: `ICalibrationImport` methods modify `SensorCalibration` in-place and return the same instance (or a new one), but callers must ensure methods are invoked in the correct order (e.g., zero method before excitation calibration if dependencies exist).
---
### 4. Dependencies
**Imports/uses from this module:**
- `DTS.Common.Import` namespace (obvious).
- `DTS.Common.Classes` (e.g., `TestSetupImportData`, `ParseParameters`).
- `DTS.Common.Enums`, `DTS.Common.Enums.Sensors` (e.g., `ZeroMethodType`, `ExcitationVoltageOption`).
- `DTS.SensorDB` (e.g., `SensorCalibration`, `SensorData`).
- `CsvHelper` (for `CsvReader` in `IParseCSVTest`).
- `DataPROWin7.DataModel` (e.g., `TestTemplate` in `IGroupImport`).
- `DTS.Common.Interface.Groups.GroupList` (e.g., `IGroup` in `IGroupImport`).
**Depended upon by (inferred):**
- Any concrete import pipeline (e.g., a `CsvImportService` or `ImportController`) that implements `IParseImport` and orchestrates `IParseVariant`, `IParseCSVSensor`, `ICalibrationImport`, `IGroupImport`, and `ILockImport`.
- Database persistence layers (implementing `IPersistImport`).
- Test setup or sensor configuration UIs that validate or preview imports (using `IParseCSVTest`, `IParseCSVSensor`).
---
### 5. Gotchas
- **`ref` parameters in `ILockImport` and `IParseVariant.Parse`**: Both `SetLock(ref ImportObject, ref StringBuilder)` and `Parse(ref ImportObject)` use `ref`, meaning the callee may reassign the object reference. Callers must ensure variables are not boxed or copied before passing.
- **`FileName` in `IParseVariant` is mutable but not validated**: The setter allows arbitrary strings; no validation or normalization is guaranteed. Callers must ensure filenames match expected patterns (e.g., case, extensions).
- **`IParseCSVSensor.Initialize` has many flags**: The `Initialize` method accepts 6 parameters, including boolean toggles (`importCreateDynamicGroups`, `useIsoCodeFilterMapping`, `useZeroForUnfiltered`). Misconfiguring these may lead to silent misbehavior (e.g., groups not created, filters ignored).
- **`IGroupImport.CreateGroups` progress reporting is optional**: The `setProgress` parameter is an `Action<double>`—callers may pass `null`, but implementations may assume its non-null and throw `NullReferenceException` if not handled.
- **No explicit error handling in interfaces**: None of the interfaces declare exceptions. Implementations may throw (e.g., `CsvHelper` parsing errors), but callers must rely on outer try/catch blocks.
- **`ILockImport.StealLock` behavior is underspecified**: The `proceed` parameters semantics (e.g., user prompt, forced override) are not documented in the interface. Implementation-specific behavior may vary.
- **`TsetSetupImportSensorInfo` typo in `IGroupImport`**: The parameter name `groupSensorLookup` and return type use `TsetSetupImportSensorInfo` (likely a typo for `TestSetupImportSensorInfo`). Verify actual type name in implementation.
> **Note**: Several types referenced (e.g., `ImportObject`, `ParseParameters`, `SensorData`, `TsetSetupImportSensorInfo`, `CSVImportTags.Tags`) are not defined in the provided source files. Their structure and behavior must be inferred from usage or consulted in other modules.

View File

@@ -0,0 +1,69 @@
---
source_files:
- Common/DTS.Common.Import/Parsers/ParseVariantBase.cs
- Common/DTS.Common.Import/Parsers/DefaultParseImport.cs
- Common/DTS.Common.Import/Parsers/DTSXMLParseImport.cs
generated_at: "2026-04-16T02:06:24.270866+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "6317dc1ace913c30"
---
# Parsers
## Documentation: Import Parsing Module
### 1. Purpose
This module provides foundational infrastructure and concrete implementations for parsing import files into a structured `ImportObject`. It defines an extensible parsing pipeline via the `IParseImport` interface, with base abstractions (`ParseVariantBase`) for variant-specific parsing logic and two concrete import processors: `DefaultParseImport` (generic, variant-driven parsing) and `DTSXMLParseImport` (XML-specific parsing with UI integration, cancellation support, and post-processing for DAS-linked hardware). The module serves as the core engine for transforming raw file inputs into domain objects used throughout the DTS import workflow.
### 2. Public Interface
#### `ParseVariantBase` (abstract class)
- **`string FileName { get; set; }`**
Gets or sets the name of the file being parsed by this variant. Set by the caller before `Parse` is invoked.
- **`abstract void Parse(ref ImportObject importObject)`**
Parses the file (identified by `FileName`) and mutates the provided `ImportObject` by adding or updating its contents. Must be implemented by derived classes.
#### `DefaultParseImport` (concrete class)
- **`DefaultParseImport(ImportObject importObject, IEnumerable<IParseVariant> parseVariants)`**
Constructor. Initializes the parser with a target `ImportObject` and a sequence of `IParseVariant` implementations to apply.
- **`ImportObject Parse(IEnumerable<string> importFiles)`**
Processes each file in `importFiles` using the configured `IParseVariant`s via a `ParseProcessor`. Returns the mutated `_importObject`.
*Note:* The same `ImportObject` instance passed to the constructor is returned (though mutated internally).
#### `DTSXMLParseImport` (concrete class)
- **`DTSXMLParseImport(ImportObject importObject, IImportNotification importNotification, Func<bool> isCancelled = null, bool skipNormalizing = false)`**
Constructor. Initializes the XML parser with a target `ImportObject`, notification sink, optional cancellation delegate, and flag to skip normalization.
- **`List<IUIItems> UIItems { get; set; }`**
Gets or sets UI-related items (e.g., progress indicators, messages) to be used by the underlying processor.
- **`ImportObject Parse(IEnumerable<string> importFiles)`**
Processes XML files via `XMLParseProcessor`, applies post-processing (`AssignLinkedDASSerials`), and returns the mutated `_importObject`.
*Note:* `UIItems` is assigned to the processor before processing.
#### `AssignLinkedDASSerials` (private method in `DTSXMLParseImport`)
- **`void AssignLinkedDASSerials(ref ImportObject importObject)`**
Post-processes `importObject.Hardware()` to populate `LinkedDASSerials` for pseudo-rack hardware: for each pseudo-rack (`IsPseudoRack() == true`), collects all hardware items whose `ParentDAS` matches the racks `SerialNumber`, and stores their serials in `LinkedDASSerials`.
### 3. Invariants
- **`ParseVariantBase.Parse` must mutate `importObject` in-place** (via `ref`), not replace it.
- **`FileName` must be set on each `IParseVariant` instance before calling `Parse(ref ImportObject)`** (enforced by caller, not by class).
- **`DTSXMLParseImport.Parse` always calls `AssignLinkedDASSerials` after processing**, ensuring `LinkedDASSerials` is populated for pseudo-rack hardware.
- **`DefaultParseImport` does not modify the `importFiles` sequence**; it only iterates over it.
- **`DTSXMLParseImport` supports cancellation** via the `_isCancelled` delegate (behavior depends on `XMLParseProcessor` implementation, not visible here).
### 4. Dependencies
- **Depends on:**
- `DTS.Common.Import.Interfaces` namespace (`IParseImport`, `IParseVariant`, `IImportNotification`, `IUIItems`).
- `DTS.Slice.Users` namespace (for `IImportNotification`, `IUIItems`).
- `ImportObject` type (used throughout; definition not provided here).
- Internal types: `ParseProcessor`, `XMLParseProcessor` (referenced but not documented here).
- **Depended on by:**
- Unknown from source alone. Likely consumed by higher-level import orchestration (e.g., UI layers, batch import services).
### 5. Gotchas
- **`DefaultParseImport` reuses the same `ImportObject` instance** passed to its constructor; callers must be aware that the returned object is the *same reference*, not a copy.
- **`DTSXMLParseImport.AssignLinkedDASSerials` only processes hardware where `IsPseudoRack()` returns `true`**; non-rack hardware is skipped.
- **`DTSXMLParseImport` does not validate `importFiles` for XML format**—assumes correctness (handled by `XMLParseProcessor`).
- **`UIItems` must be set on `DTSXMLParseImport` *before* calling `Parse`** to be effective (assigned to processor inside `Parse`).
- **`skipNormalizing` flag behavior is not visible in this module**—depends on `XMLParseProcessor` implementation.
- **`AssignLinkedDASSerials` uses LINQ query materialization (`as string[] ?? matches.ToArray()`) but does not guard against null `Hardware()`**—potential `NullReferenceException` if `importObject.Hardware()` returns `null`.

View File

@@ -0,0 +1,143 @@
---
source_files:
- Common/DTS.Common.Import/Parsers/CSV/AbstractCSVParser.cs
- Common/DTS.Common.Import/Parsers/CSV/Version0CSVTestParser.cs
- Common/DTS.Common.Import/Parsers/CSV/CSVFile.cs
- Common/DTS.Common.Import/Parsers/CSV/Version6CSVTestParser.cs
- Common/DTS.Common.Import/Parsers/CSV/Version3CSVSensorParser.cs
- Common/DTS.Common.Import/Parsers/CSV/Version5CSVTestParser.cs
- Common/DTS.Common.Import/Parsers/CSV/Version4CSVSensorParser.cs
- Common/DTS.Common.Import/Parsers/CSV/CSVGroupImport.cs
- Common/DTS.Common.Import/Parsers/CSV/DTSCSVTestSetupParser.cs
- Common/DTS.Common.Import/Parsers/CSV/DTSCSVSensorsParser.cs
generated_at: "2026-04-16T02:08:24.981291+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "2bff5b23b8f73415"
---
# CSV Import Module Documentation
## 1. Purpose
This module provides a versioned CSV parsing infrastructure for importing sensor and test setup data into the DTS system. It supports two distinct import workflows: **sensor data import** (handled by `DTSCSVSensorsParser`) and **test setup data import** (handled by `DTSCSVTestSetupParser`). The module uses an abstract base class (`AbstractCSVParser`) and concrete version-specific parsers to handle evolving CSV formats across versions 0 through 6. It decouples parsing logic from business logic, delegates group creation to `CSVGroupImport`, and integrates with calibration, sensor, and hardware data models to construct `TestTemplate` and `ImportObject` instances for downstream processing.
## 2. Public Interface
### `AbstractCSVParser` (abstract base class)
- **`int Version { get; }`**
Returns the CSV format version supported by this parser (e.g., 3, 4).
- **`void Initialize(ICalibrationImport import, ZeroMethodOptions zmOptions, IImportNotification importNotification, bool importCreateDynamicGroups, bool useISOCodeFilterMapping, bool useZeroForUnfiltered)`**
Injects dependencies and configuration flags required for parsing. Must be called before `ParseVersion`.
- **`abstract void ParseVersion(CSVImportTags.Tags field, string val, ParseParameters pp)`**
Parses a single field (`field`) from a sensor CSV row, using value `val`. Errors are appended to `pp.Errors`. Behavior is version-specific.
### `Version0CSVTestParser`
- **`int Version => 0`**
- **`void ParseVersion(CsvReader csvReader, TestSetupImportData tsid)`**
Parses test setup metadata (e.g., `PostTriggerSec`, `SampleRate`, `RecordingMode`) from a CSV with a simple header-value pair format. Reads two rows: header names and values.
### `Version3CSVSensorParser`
- **`int Version => 3`**
- **`void ParseVersion(CSVImportTags.Tags field, string sVal, ParseParameters pp)`**
Handles `GroupName` and `GroupType` fields. Enforces non-empty values, uniqueness of sensor serial numbers in group lookups, and consistency of `TestObject` per group (throws `Exception("Parse error")` on conflict if `ImportCreateDynamicGroups` is false).
### `Version4CSVSensorParser`
- **`int Version => 4`**
- **`void ParseVersion(CSVImportTags.Tags field, string sVal, ParseParameters pp)`**
Parses DAS-related (`DASSerialNumber`, `DASChannelIndex`), streaming (`StreamProfile`, `UDPAddress`, `TimeChannelId`, `DataChannelId`, `TmNSConfig`, `IRIGTimeDataPacketIntervalMS`, `TMATSIntervalMS`), UART (`BaudRate`, `DataBits`, `StopBits`, `Parity`, `DataFormat`), and user-defined fields (`TestUserCode`, `TestUserChannelName`, `TestIsoCode`, `TestIsoChannelName`). Updates `pp.SensorData` and lookup dictionaries.
### `Version5CSVTestParser`
- **`int Version => 5`**
- **`void ParseVersion(CsvReader csvReader, TestSetupImportData tsid)`**
Parses clock synchronization settings (e.g., `ClockMasterInputType`, `ClockMasterOutputType`, `ManageClocksOutsideDPMaster`) from a header-value row format.
### `Version6CSVTestParser`
- **`int Version => 6`**
- **`void ParseVersion(CsvReader csvReader, TestSetupImportData tsid)`**
Parses per-DAS hardware configuration (e.g., `DASSerial`, `DASSampleRate`, `PTPDomainId`, `ClockMaster`) in a table format. Reads header row, then iterates rows until an empty line or non-matching field is encountered.
### `CSVFile`
- **`static bool IsInUse(string filename)`**
Returns `true` if the file is locked (IOException on `File.ReadAllLines`), else `false`.
- **`static bool IsCSVFileForTestSetupImport(string filename, CsvImportOptions csvImportOptions)`**
Returns `true` if the first field of the first row is `"Version"`.
- **`static int GetCsvVersion(string filename)`**
Returns the integer value in the first column of the second row if the first row starts with `"Version"`, else `0`.
- **`int LineCount { get; }`**
Returns the number of lines in the file (0 on error or null filename).
### `CSVGroupImport`
- **`ParseParameters ParseParameters { get; set; }`**
- **`Tuple<TestTemplate, List<IGroup>> CreateGroups(List<SensorData> sensors, Dictionary<string, List<TsetSetupImportSensorInfo>> groupSensorLookup, TestTemplate testTemplate, bool createDynamicGroups, List<IGroup> staticGroups, Action<double> setProgress)`**
Creates `IGroup` instances from `groupSensorLookup`, assigns sensor/channel properties (ISO/user codes, DAS mapping, calibration defaults), and adds groups to `testTemplate`. Updates `setProgress`.
- **`Dictionary<string, List<TsetSetupImportSensorInfo>> GetGroupSensorLookup(List<SensorData> sensors, Dictionary<string, string> sensorGroupNameLookup, Dictionary<string, List<string>> groupNameSensorListLookup)`**
Builds a mapping from group name to list of `TsetSetupImportSensorInfo` (containing sensor/DAS parameters) using either `sensorGroupNameLookup` or `groupNameSensorListLookup`.
### `DTSCSVTestSetupParser`
- **`void Parse(ref ImportObject importObject)`**
Orchestrates test setup import: validates CSV format, parses test setup data via `ParseTestSetup`, creates `TestTemplate`, assigns hardware, clock sync, and tags, then delegates group creation to `AssignGroupsToTestSetup`. Adds result to `importObject`.
### `DTSCSVSensorsParser`
- **`void Parse(ref ImportObject importObject)`**
Orchestrates sensor import: reads CSV, populates `SensorData` and `SensorCalibration` objects via `PopulateSensor`, handles sensitivity unit corrections, and adds sensors/calibrations to `importObject`. Reports errors and warnings.
## 3. Invariants
- **CSV Format Validation**:
- A CSV is recognized as a test setup file if its first field is `"Version"`.
- Sensor CSVs must have recognizable column headers (else `ImportSensorsPreviewControl_NoColumnsInTDCCSV` error).
- **Version Consistency**:
- `Version0CSVTestParser` and `Version5CSVTestParser` expect header-value pairs.
- `Version6CSVTestParser` expects a table format with header row followed by data rows until an empty line.
- `DTSCSVTestSetupParser.ParseTestSetup` uses `CSVFile.GetCsvVersion` to determine which parsers to invoke; version 6 triggers all parsers (0 + 5 + 6).
- **Sensor Grouping**:
- `Version3CSVSensorParser` enforces that a group name maps to a single `SensorTestObject` unless `ImportCreateDynamicGroups` is true.
- Duplicate sensor serial numbers in `sensorGroupNameLookup` or `sensorGroupTypeLookup` are rejected with errors.
- **Calibration & Defaults**:
- `DTSCSVSensorsParser` applies user-specific defaults for squib/digital output settings.
- Sensitivity units are corrected from `mVperVperEU` to `mVperEU` for non-proportional calibrations.
- Zero method is initialized from `pp.SensorCal.ZeroMethods.Methods[0]` during `PostParse`.
- **Hardware Mapping**:
- DAS hardware is added to `TestTemplate` only if version ≥ 6 (per `DTSCSVTestSetupParser` comment FB 43815).
- DAS sample rates are set in both `t.SetSampleRateForHardware` and `t.DASSampleRateList`.
## 4. Dependencies
### Imports/References:
- **Core Types**: `DTS.Common.Classes`, `DTS.Common.Enums`, `DTS.Common.Import.Interfaces`, `CsvHelper`, `System.IO`, `System.Collections.Generic`.
- **Hardware/Storage**: `DataPROWin7.DataModel`, `DTS.SensorDB`, `DTS.Common.Storage`.
- **Import Infrastructure**: `ParseVariantBase`, `ParseParameters`, `ImportObject`, `TestSetupImportData`, `SensorData`, `SensorCalibration`, `IGroup`, `IGroupImport`, `ICalibrationImport`, `IImportNotification`.
- **CSV Tags**: `CSVImportTags` (static class providing `GetTagForString`, `GetVersionForTag`).
- **Factories**: `CSVTestParserFactory`, `CSVSensorParserFactory`.
### Used By:
- `DTSCSVTestSetupParser` is invoked by the test setup import pipeline.
- `DTSCSVSensorsParser` is invoked by the sensor import pipeline.
- `CSVGroupImport` is used by `DTSCSVTestSetupParser` to create groups.
## 5. Gotchas
- **Version 6 CSV Parsing Quirk**:
In `DTSCSVSensorsParser.ParseSensor`, if version is 6, the parser skips rows until it finds a tag with version ≤ 4 (to avoid parsing test setup-specific rows as sensor data). This assumes sensor and test setup data are in the same file but separated by an empty line.
- **Exception Thrown in Group Parsing**:
`Version3CSVSensorParser.ParseVersion` throws `new Exception("Parse error")` on group/test object conflict (if `ImportCreateDynamicGroups` is false). This is caught by `DTSCSVSensorsParser` and reported as a sensor error, but the exception itself is non-descriptive.
- **Sensitivity Unit Correction**:
`DTSCSVSensorsParser` silently changes `SensitivityUnits` from `mVperVperEU` to `mVperEU` for non-proportional calibrations. This is logged as a warning but may cause unexpected calibration behavior.
- **DAS Hardware Lookup**:
`CSVGroupImport.CreateGroups` uses `DbOperations.GetChannelSettingDefaults()` and `GroupHelper.GetDAS` to map DAS serial numbers to IDs. If DAS is not in `DASHardwareList.GetAllHardware()`, channel DAS ID remains unset.
- **Zero Method Initialization**:
`PostParse` in `DTSCSVSensorsParser` initializes `pp.ZeroType`, `pp.ZeroStart`, `pp.ZeroEnd` from `pp.SensorCal.ZeroMethods.Methods[0]`. If `ZeroMethods` is uninitialized or empty, this may cause `IndexOutOfRangeException`.
- **No Validation of Sensor Serial Number Format**:
Sensors with serial numbers starting with `Constants.ISO_CH_ONLY_PREFIX` bypass some validation (e.g., sensitivity/capacity checks), but the prefix is not defined in the provided source.
- **Error Reporting Inconsistency**:
`DTSCSVSensorsParser` aggregates errors per line and reports them as a single error string, while `DTSCSVTestSetupParser` reports errors via `IImportNotification.ReportErrors`. Error severity and continuation behavior differ between parsers.
- **File Lock Detection Limitation**:
`CSVFile.IsInUse` uses `File.ReadAllLines`, which may not reliably detect all lock states (e.g., file opened for append-only).

View File

@@ -0,0 +1,115 @@
---
source_files:
- Common/DTS.Common.Import/Parsers/EQX/EQXTestSetupParser.cs
- Common/DTS.Common.Import/Parsers/EQX/EQXGroupImport.cs
- Common/DTS.Common.Import/Parsers/EQX/EQXSensorsParser.cs
generated_at: "2026-04-16T02:08:30.752253+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "ce57e1c33a0387ff"
---
# Documentation: EQX Test Setup Parser Module
## 1. Purpose
This module provides parsing logic for EQX files that contain test setup information (i.e., groupings of sensors into test groups and assignment to a test template). It is part of the `DTS.Common.Import` namespace and is specifically responsible for interpreting group-to-sensor mappings defined in an EQX file, constructing `TestTemplate`, static `IGroup`, and `SensorData` objects accordingly, and integrating them into the `ImportObject`. It depends on prior parsing of sensor and calibration data (handled by `EQXSensorsParser`) and uses `EQXGroupImport` to implement group creation logic. The parser aborts the import if the EQX file lacks test setup data (e.g., no `GroupNameTestObjectLookup`).
## 2. Public Interface
### `EQXTestSetupParser` class
**Constructor**
```csharp
public EQXTestSetupParser(
IImportNotification importNotification,
EqxImportOptions eqxImportOptions,
IGroupImport groupImport,
EquipmentExchange.EQXSensorDatabase eqxSensorDatabase,
bool createDynamicGroups)
```
Initializes the parser with dependencies required for parsing and group creation.
- `importNotification`: Used for reporting errors and progress.
- `eqxImportOptions`: Configuration options for EQX import.
- `groupImport`: Implementation of `IGroupImport` (typically `EQXGroupImport`) for group creation logic.
- `eqxSensorDatabase`: Pre-populated sensor database from `EQXSensorsParser`.
- `createDynamicGroups`: Flag indicating whether dynamic groups should be created (if `false`, static groups are created).
**Override Method**
```csharp
public override void Parse(ref ImportObject importObject)
```
Main entry point for parsing test setup data from the EQX file.
- Validates `FileName` and `importObject` (throws `ArgumentNullException` if `importObject` is `null`).
- Checks for presence of test setup data in `sensorImportData.GroupNameTestObjectLookup`; if missing, adds a critical error and aborts.
- Assigns group-to-object and group-to-sensor lookups from the database to `importObject`.
- Extracts setup name from XML using `ParseSetupName`, falls back to file name.
- Skips if a test setup with the same name already exists in `importObject`.
- Calls `FixCalibrations` to ensure sensors use the correct calibration from import.
- Delegates group creation to `AssignGroupsToTestSetup`.
- Cleans sensor data placeholders, clears existing sensors, adds cleaned sensors, and adds the resulting `TestTemplate` and static groups to `importObject`.
- Sets `importObject.TestSetupImportFileFormat`.
**Private Helper Methods**
```csharp
private void FixCalibrations(ImportObject importObject)
```
For each sensor in `importObject`, retrieves calibrations via `CalibrationLookup(sensor.SerialNumber)`, sorts them, and assigns the first (earliest) calibration to `sensor.Calibration`. Logs exceptions via `APILogger`.
```csharp
private Tuple<TestTemplate, List<IGroup>, List<SensorData>> AssignGroupsToTestSetup(
TestTemplate testTemplate,
ImportObject importObject,
Dictionary<string, List<string>> groupNameSensorListLookup,
IGroupImport groupImport,
IImportNotification importNotification)
```
Implements the group assignment logic (moved here per FB 36879).
- Reverses channel order using `GroupHelper.ReverseChannelOrder`.
- Calls `groupImport.GetGroupSensorLookup` to build `groupSensorLookup`.
- Normalizes sensor IDs via `GroupHelper.NormalizeSensorIds`.
- Re-applies `FixCalibrations` (to revert potential calibration resets from normalization).
- Calls `groupImport.CreateGroups(...)` to generate groups and test setup.
- Returns `Tuple<TestTemplate, List<IGroup>, List<SensorData>>`, or `null` on failure (with error reported via `importNotification`).
- Returns `null` for `testTemplate`/`staticGroups` if `groupImport.CreateGroups` returns `null`.
```csharp
private string ParseSetupName(string filename)
```
Parses the `<SetupName>` value from the first `<Fields>` element under `<Sensors>` in the EQX XML file. Returns empty string on any failure (including missing element or XML parse error).
## 3. Invariants
- **`importObject` must be non-null** at start of `Parse`; otherwise, `ArgumentNullException` is thrown.
- **`GroupNameTestObjectLookup` must be non-null and non-empty** in `sensorImportData`; otherwise, a critical error is added and parsing aborts.
- **Calibrations must be sorted before selecting the first one** in `FixCalibrations`; sorting is performed explicitly.
- **Sensors are cleared and re-added** after cleaning (`GroupHelper.CleanUneededSensorDataPlaceHolder`) to ensure consistency.
- **Test setup name uniqueness is enforced**: if a test setup with the same name already exists in `importObject.TestSetups()`, the method returns early without modifying the object.
- **`EQXGroupImport.CreateGroups` may return `null`**, in which case `AssignGroupsToTestSetup` returns a tuple of `null` values and no groups/test setup are added.
## 4. Dependencies
### Dependencies *of* this module:
- **`EQXSensorsParser`**: Must run first to populate `ImportObject.Sensors()`, `ImportObject.CalibrationsLookup()`, and `EquipmentExchange.EQXSensorDatabase` (via `EQXSensorDatabase.Read`).
- **`EquipmentExchange.EQXSensorDatabase`**: Must be pre-populated with sensor, calibration, and group mapping data from the EQX file.
- **`IGroupImport`**: Typically implemented by `EQXGroupImport`; provides `GetGroupSensorLookup` and `CreateGroups`.
- **`GroupHelper`**: Used for `ReverseChannelOrder`, `NormalizeSensorIds`, and `CleanUneededSensorDataPlaceHolder`.
- **`DbOperations`**: Referenced indirectly (e.g., `GetChannelSettingDefaults`, `TagsGet`, etc.) via `EQXGroupImport` and `EQXSensorsParser`.
- **`APILogger`**: Used for logging failures in `FixCalibrations`.
- **`StringResources`**: Used for error messages (e.g., `Import_EQXFileNotForTestSetup`).
### Dependencies *on* this module:
- **`EQXSensorsParser`**: Must be invoked before `EQXTestSetupParser` to ensure sensor and calibration data are available.
- **`ImportObject` consumers**: Any downstream logic that consumes `TestTemplate`, `StaticGroups`, or `Sensors` after import will depend on this parsers output.
## 5. Gotchas
- **Calibration resets after normalization**: `GroupHelper.NormalizeSensorIds` may reset sensor calibrations; `FixCalibrations` is called *twice* (before and after normalization) to mitigate this (see FB 44105 comment in `AssignGroupsToTestSetup`).
- **Setup name extraction is fragile**: `ParseSetupName` silently returns `string.Empty` on any XML parsing error or missing `<SetupName>` element.
- **No overwrite of existing test setups**: If a test setup with the same name already exists, `Parse` returns early without warning or modification (only silent skip).
- **Group creation may silently fail**: If `groupImport.CreateGroups` throws or returns `null`, `AssignGroupsToTestSetup` returns `null` and no groups/test setup are added, but only the exception message is reported via `importNotification.ReportErrors` (no additional context).
- **`ImportCreateDynamicGroups` flag is not used directly in this class**: It is passed to `EQXGroupImport.CreateGroups` via `EQXTestSetupParser` constructor, but the flag is stored as a property in `EQXSensorsParser`, not `EQXTestSetupParser`.
- **Excitation errors are computed but not reported**: `EQXSensorsParser.GetExcitationErrors()` collects errors but the `TODO Report the errors` comment indicates they are not currently surfaced (only in `EQXSensorsParser`, not here).
- **`SensorData` is modified in-place**: `FixCalibrations` mutates `sensor.Calibration` directly; callers must be aware of side effects.
- **No handling of duplicate sensor serial numbers**: If duplicate serial numbers exist in the import, behavior is undefined (e.g., `sensorLookup` in `EQXGroupImport` will only retain the last one).

View File

@@ -0,0 +1,338 @@
---
source_files:
- Common/DTS.Common.Import/Persist/SaveServer.cs
- Common/DTS.Common.Import/Persist/SaveGroupTemplates.cs
- Common/DTS.Common.Import/Persist/SaveVariantBase.cs
- Common/DTS.Common.Import/Persist/SaveGlobalSettings.cs
- Common/DTS.Common.Import/Persist/PersistCalculator.cs
- Common/DTS.Common.Import/Persist/SaveSensorModels.cs
- Common/DTS.Common.Import/Persist/SaveTestEngineerDetails.cs
- Common/DTS.Common.Import/Persist/SaveLabDetails.cs
- Common/DTS.Common.Import/Persist/SaveUsers.cs
- Common/DTS.Common.Import/Persist/SaveCustomerDetails.cs
- Common/DTS.Common.Import/Persist/SaveHardware.cs
- Common/DTS.Common.Import/Persist/SaveTestSetupHelper.cs
- Common/DTS.Common.Import/Persist/SaveGroups.cs
- Common/DTS.Common.Import/Persist/SaveCustomChannels.cs
- Common/DTS.Common.Import/Persist/SaveTestSetup.cs
- Common/DTS.Common.Import/Persist/SaveCheckoutTestSetup.cs
generated_at: "2026-04-16T02:08:08.051368+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "695fcda61b684601"
---
# Documentation: `DTS.Common.Import.Persist` Module
## 1. Purpose
This module provides a family of concrete implementations for persisting various types of imported data during a data import workflow. Each class (`Save*`) corresponds to a distinct category of data (e.g., hardware, groups, test setups, users, custom channels) and encapsulates the logic required to commit that data to the target database or system. All implementations inherit from the abstract base class `SaveVariantBase`, which provides shared infrastructure for progress tracking, cancellation support, and dependency injection of core services (`IPersistCalculator`, `IImportNotification`). The module serves as the persistence layer of the import pipeline, transforming in-memory `ImportObject` data into persistent system state.
## 2. Public Interface
All classes listed below are `public` (except `SaveServer`, which is `internal`) and inherit from `SaveVariantBase`. Each implements the `Save()` method.
### `SaveVariantBase` (abstract base class)
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public abstract class SaveVariantBase : IPersistImport`
- **Constructor**:
```csharp
protected SaveVariantBase(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Properties**:
- `protected ImportObject _importObject`
- `protected readonly IPersistCalculator _persistCalculator`
- `protected readonly IImportNotification _importNotification`
- `protected readonly Func<bool> IsCancelled`
- **Methods**:
- `public abstract void Save()`
### `SaveServer` (internal)
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `class SaveServer : SaveVariantBase`
- **Constructor**:
```csharp
public SaveServer(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()` → **Throws `NotImplementedException`**. This class is a placeholder and not functional.
### `SaveGroupTemplates`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveGroupTemplates : SaveVariantBase`
- **Constructor**:
```csharp
public SaveGroupTemplates(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
Iterates over `_importObject.GroupTemplates()`. For each template:
- Checks cancellation (`IsCancelled()`), returns early if true.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(_persistCalculator.ProgressValue)`.
**Note**: Does *not* actually commit group templates to storage; only updates progress.
### `SaveGlobalSettings`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveGlobalSettings : SaveVariantBase`
- **Constructor**:
```csharp
public SaveGlobalSettings(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
Iterates over `_importObject.GlobalSettings()`. For each setting `g`:
- Checks cancellation, returns early if true.
- Calls `SettingsDB.SetGlobalValue(g.Key, g.Value)`.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(...)`.
### `SaveSensorModels`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveSensorModels : SaveVariantBase`
- **Properties**:
- `public User CurrentUser { get; set; }`
- **Constructor**:
```csharp
public SaveSensorModels(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
Iterates over `_importObject.SensorModels()`. For each model `st`:
- Checks cancellation, returns early if true.
- Calls `SensorModelCollection.SensorModelList.Commit(st, CurrentUser.UserName)`.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(...)`.
### `SaveTestEngineerDetails`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveTestEngineerDetails : SaveVariantBase`
- **Constructor**:
```csharp
public SaveTestEngineerDetails(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
- First, calls `_importNotification.SetStatus.Invoke(...)` with `ExtraStatus = ImportExtraStatus.ReadingEngineerDetails`.
- Iterates over `_importObject.TestEngineerDetails()`. For each detail `t`:
- Checks cancellation, returns early if true.
- Calls `TestEngineerDetailsList.TestEngineerList.AddTestEngineer(new TestEngineerDetails(t))`.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(...)`.
### `SaveLabDetails`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveLabDetails : SaveVariantBase`
- **Constructor**:
```csharp
public SaveLabDetails(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
- First, calls `_importNotification.SetStatus.Invoke(...)` with `ExtraStatus = ImportExtraStatus.ReadingLabDetails`.
- Declares `bool invalidLabDetails = false;` (but does *not* use it further).
- Iterates over `_importObject.LabDetails()`. For each detail `l`:
- Checks cancellation, returns early if true.
- If `l.IsInvalidBlank()` is true, sets `invalidLabDetails = true`.
- Otherwise, calls `LabratoryDetailsList.AddLab(new LabratoryDetails(l))`.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(...)`.
### `SaveCustomerDetails`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveCustomerDetails : SaveVariantBase`
- **Properties**:
- `bool invalidCustomerDetails = false;` (instance field, not used beyond assignment)
- **Constructor**:
```csharp
public SaveCustomerDetails(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
- First, calls `_importNotification.SetStatus.Invoke(...)` with `ExtraStatus = ImportExtraStatus.ReadingLabDetails` *(note: likely a copy-paste error; should be `ReadingCustomerDetails`)*.
- Iterates over `_importObject.CustomerDetails()`. For each detail `c`:
- Checks cancellation, returns early if true.
- If `c.IsInvalidBlank()` is true, sets `invalidCustomerDetails = true`.
- Otherwise, calls `CustomerDetailsList.AddCustomer(new CustomerDetails(c))`.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(...)`.
### `SaveUsers`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveUsers : SaveVariantBase`
- **Properties**:
- `public IUIItems[] UIItems { get; set; }`
- `public User CurrentUser { get; set; }`
- **Constructor**:
```csharp
public SaveUsers(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
- First, calls `_importNotification.SetStatus.Invoke(...)` with `ExtraStatus = ImportExtraStatus.ReadingUsers`.
- Iterates over `_importObject.Users()`. For each `user`:
- Checks cancellation, returns early if true.
- Asserts `CurrentUser.IsAdmin` via `System.Diagnostics.Trace.Assert(...)` (throws in debug if false).
- Calls `UserCollection.UsersList.Commit(user, CurrentUser.UserName, UIItems)`.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(...)`.
### `SaveHardware`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveHardware : SaveVariantBase`
- **Properties**:
- `public Dictionary<int, int> OldDASIdToNewDASId { get; set; }`
- `public Dictionary<int, List<IISOHardware>> TestIdToHardware { get; set; }`
- **Constructor**:
```csharp
public SaveHardware(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
- First, calls `_importNotification.SetStatus.Invoke(...)` with `ExtraStatus = ImportExtraStatus.ReadingHardware`.
- Calls `_importObject.Hardware().ToList().Sort()` (sorts hardware list in-place).
- Iterates over `_importObject.Hardware()`. For each hardware `h`:
- Checks cancellation, returns early if true.
- Stores `oldId = h.DASId`.
- Calls `DASHardwareList.GetList().Commit(h, false, true)`.
- If `h.DASId != oldId`, adds mapping `OldDASIdToNewDASId[oldId] = h.DASId`.
- Gets `iso = h.GetHardware()`. If `iso.TestId != null`, adds `iso` to `TestIdToHardware[(int)iso.TestId]`.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_persistCalculator.AddDone(h.Channels?.Length ?? 0)` (adds number of channels).
- Calls `_importNotification.SetProgress(...)`.
### `SaveGroups`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveGroups : SaveVariantBase`
- **Properties**:
- `public bool CanCurrentUserCommitChannelCodes { get; set; } = true`
- `public IChannelSetting DefaultZeroMethod { get; set; }`
- `public IChannelSetting DefaultZeroStart { get; set; }`
- `public IChannelSetting DefaultZeroEnd { get; set; }`
- `public IChannelSetting DefaultInitialOffset { get; set; }`
- `public Dictionary<int?, int> OldGroupIdToNewGroupId { get; set; }`
- `private readonly SaveHardware _saveHardware`
- **Constructor**:
```csharp
public SaveGroups(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, SaveHardware saveHardware, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
- First, calls `_importNotification.SetStatus.Invoke(...)` with `ExtraStatus = ImportExtraStatus.ReadingGroups`.
- Builds `idToDASId` lookup from `DASHardwareList.GetAllHardware()`.
- Retrieves channel defaults from `DbOperations.GetChannelSettingDefaults()` and assigns to `Default*` properties.
- Iterates over `_importObject.StaticGroups()`. For each group `g`:
- Checks cancellation, returns early if true.
- If `!g.Embedded`:
- For each channel `ch` in `g.GroupChannelList`:
- Updates `ch.SensorId` using `_importObject.OldSensorDatabaseIdsToNew()`.
- Updates `ch.DASId` using `_saveHardware.OldDASIdToNewDASId`; if missing, sets `ch.DASId = -1` and tracks group name in `missingDASGroupList`.
- Calls `SaveSensor.FixMissingZeroMethodParameter(...)` and `SaveSensor.FixMissingInitialOffset(...)` (methods not defined in source).
- Calls `SetGroupId(g)` (static helper) to resolve group ID from DB.
- Calls `FixGroupHardware(g, idToDASId)` (static helper) to resolve hardware IDs.
- Calls `g.Save(g.GroupChannelList.ToArray(), CanCurrentUserCommitChannelCodes)`.
- Maps `OldGroupIdToNewGroupId[normalizedStaticGroupId] = g.Id`.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(...)`.
- If `missingDASGroupList.Any()`, reports a warning via `_importObject.AddError(...)`.
### `SaveCustomChannels`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveCustomChannels : SaveVariantBase`
- **Properties**:
- `public ISO13499FileDb IsoDb { get; set; }`
- `public Dictionary<string, string> CustomChannelTextIdToOldChannelId { get; set; }`
- `public Dictionary<string, TestObjectChannel> CustomChannelOldChannelIdToChannel { get; set; }`
- **Constructor**:
```csharp
public SaveCustomChannels(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification, Func<bool> isCancelled = null)
```
- **Methods**:
- `public override void Save()`
- Early return if `_importObject.CustomChannels()` is null or empty.
- Iterates over custom channels in reverse order. For each `cc`:
- Checks cancellation, returns early if true.
- Stores `oldid = cc.Id`.
- Tries to find existing channel by ISO code (`CustomChannelList.List.GetChannelByISOCode(...)`). If found, uses its ID; otherwise, clears `cc.Id` and inserts.
- Commits `new CustomChannel(cc)`.
- For each group template in `_importObject.GroupTemplates()`:
- If channel matches, updates its ID to `newid`.
- Updates group templates and associated groups, preserving sensor/hardware IDs.
- Populates `CustomChannelTextIdToOldChannelId` and `CustomChannelOldChannelIdToChannel` for later remapping.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(...)`.
- **Commented-out GC.Collect** indicates memory pressure concerns.
- Calls `CustomChannelList.List.UpdateAll()`.
### `SaveTestSetup`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveTestSetup : SaveVariantBase`
- **Properties**:
- `private readonly SaveCustomChannels _saveCustomChannels`
- `private readonly SaveHardware _saveHardware`
- `private readonly SaveGroups _saveGroups`
- `public User CurrentUser { get; set; }`
- `public string TestSetupName { get; set; }`
- **Constructor**:
```csharp
public SaveTestSetup(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification,
SaveCustomChannels saveCustomChannels, SaveHardware saveHardware, SaveGroups saveGroups, Func<bool> isCancelled = null)
```
- **Methods**:
- `private void ConfigureDefaultRunTestTestSetup(TestTemplate testSetup)`
Sets many boolean/numeric properties (e.g., `DoROIDownload = true`, `ExportFormats = rdfadc`, etc.) for run-test mode.
- `public override void Save()`
- If `ImportFileFormat == SingleTestSetup` and `TestSetupName` is set, overrides `Name`, `TestId`, and calls `ConfigureDefaultRunTestTestSetup(...)`.
- Calls `SaveTestSetupHelper.UpdateLevelTriggers(...)` to remap level trigger channel IDs.
- Calls `SaveTestSetupHelper.DeleteExistingTestSetups(...)` to delete existing test setups.
- Calls `SaveTestSetupHelper.PopulateHarwareLookup()` to build `hardwareLookup`.
- Iterates over `_importObject.TestSetups()`. For each `t`:
- Calls `SaveTestSetupHelper.FixDasAff(...)` to fix DAS AAF rates.
- Stores `idPriorToCommit = t.Id`, then sets `t.Id = 0`.
- For each channel in `t.ChannelsForGroup`:
- Updates `SensorId` and `DASId` using mappings from `_importObject` and `_saveHardware`.
- Calls `SaveSensor.FixMissingZeroMethodParameter(...)` and `SaveSensor.FixMissingInitialOffset(...)`.
- Updates `hardwareIncluded`/`hardwareRemoved` lists using `OldDASIdToNewDASId`.
- Calls `SaveTestSetupHelper.AddHardwareFromEmbeddedGroups(...)`.
- Calls `t.ReplaceCalculatedChannel(oldIdToNewIdLookup)`.
- Checks cancellation, returns early if true.
- Ensures `t.TestSetupUniqueId` is set (generates GUID if null).
- Commits `t` via `TestTemplateList.TestTemplatesList.Commit(t, false)`.
- If `idPriorToCommit > 0`, updates `TestId` on associated hardware in `_saveHardware.TestIdToHardware`.
- Calls `_persistCalculator.AddDone()` (increments by 1).
- Calls `_importNotification.SetProgress(...)`.
- Clears `oldIdToNewIdLookup`.
- Sets status to `ImportExtraStatus.None`/`PossibleStatus.Working`.
### `SaveCheckoutTestSetup`
- **Namespace**: `DTS.Common.Import.Persist`
- **Signature**: `public class SaveCheckoutTestSetup : SaveVariantBase`
- **Properties**:
- `private readonly SaveCustomChannels _saveCustomChannels`
- `private readonly SaveHardware _saveHardware`
- `private readonly SaveGroups _saveGroups`
- `private readonly string _setupName`
- `public User CurrentUser { get; set; }`
- **Constructor**:
```csharp
public SaveCheckoutTestSetup(ImportObject importObject, IPersistCalculator persistCalculator, IImportNotification importNotification,
SaveCustomChannels saveCustomChannels, SaveHardware saveHardware, SaveGroups saveGroups, Func<bool> isCancelled = null, string setupName = null)
```
- **Methods**:
- `private void ConfigureCheckoutTestSetup(TestTemplate TestSetupCheckout)`
Sets many boolean properties for checkout mode (e.g., `CheckoutMode = true`, `AllowMissingSensors = true`, `ExportFormats = none`, etc.).
-

View File

@@ -0,0 +1,38 @@
---
source_files:
- Common/DTS.Common.Import/Properties/AssemblyInfo.cs
generated_at: "2026-04-16T02:06:58.956349+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "528b9a9492c4b445"
---
# Properties
## Documentation Page: `DTS.Common.Import` Assembly
### 1. Purpose
This module is the `DTS.Common.Import` .NET assembly, a foundational component in the DTS (Data Transfer System) codebase. Its primary purpose is to serve as a shared library for import-related functionality—though the provided source file itself contains only assembly metadata and no implementation code. It acts as a container for types related to data import operations (e.g., parsing, validation, transformation), likely referenced by other modules in the system (e.g., `DTS.Common`, `DTS.ImportService`). The assembly is not executable on its own; it is intended to be consumed as a dependency.
### 2. Public Interface
**No public types or members are defined in this file.**
The file `AssemblyInfo.cs` contains only assembly-level attributes (e.g., version, title, COM visibility). It does not declare any classes, interfaces, methods, properties, or fields. Therefore, there is no public interface to document here.
### 3. Invariants
- **Assembly identity is fixed at build time**: The assembly version is explicitly set to `1.0.0.0` via `[assembly: AssemblyVersion("1.0.0.0")]` and `[assembly: AssemblyFileVersion("1.0.0.0")]`.
- **COM visibility is disabled**: `[assembly: ComVisible(false)]` ensures types in this assembly are not exposed to COM by default.
- **GUID is stable**: The typelib GUID `c1bc06f4-8657-4892-bc4d-1064da01c4c7` uniquely identifies this assembly for COM interop (though COM exposure is disabled).
- **No runtime behavior**: This file contributes no executable logic; invariants apply only to metadata and build-time configuration.
### 4. Dependencies
- **Dependencies on .NET Framework**: This assembly depends on core .NET types via `System.Reflection`, `System.Runtime.CompilerServices`, and `System.Runtime.InteropServices`.
- **Consumed by**: Other modules in the DTS ecosystem (e.g., `DTS.ImportService`, `DTS.Common`) are expected to reference this assembly to access import-related types (not visible in this file).
- **No internal dependencies**: Since this file contains only attributes, it has no internal dependencies on other types or modules within the `DTS.Common.Import` assembly.
### 5. Gotchas
- **Misleading file name**: The file `AssemblyInfo.cs` is standard for assembly metadata but contains no business logic. Developers should not expect to find import-related functionality here.
- **Versioning convention**: The use of `[assembly: AssemblyVersion("1.0.0.0")]` with no wildcard (`*`) means build/revision numbers are static unless manually updated. This may conflict with CI/CD practices that auto-increment versions.
- **COM interop is disabled**: If this assembly is intended for COM consumption (e.g., legacy integration), the `ComVisible(false)` setting must be overridden per-type (not shown here), or `ComVisible(true)` must be enabled at the assembly level.
- **No documentation on public types**: The absence of public members in this file implies that all meaningful types reside in other source files (not provided), which may require separate documentation.
None identified from source alone beyond the above.

View File

@@ -0,0 +1,183 @@
---
source_files:
- Common/DTS.Common.Import/XML/XMLPre20ParseGroupTemplates.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomDirections.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomTestObjects.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomPositions.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomFineLoc3s.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomFineLoc2s.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomFineLoc1s.cs
- Common/DTS.Common.Import/XML/XMLParseSensorModels.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomFilterClasses.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomChannels.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomMainLocations.cs
- Common/DTS.Common.Import/XML/XMLParseMMECustomPhysicalDimensions.cs
- Common/DTS.Common.Import/XML/XMLPre20ParseSensors.cs
- Common/DTS.Common.Import/XML/XMLParseUsers.cs
- Common/DTS.Common.Import/XML/XMLParseBase.cs
- Common/DTS.Common.Import/XML/XMLParseTestEngineerDetails.cs
- Common/DTS.Common.Import/XML/XMLParseLabDetails.cs
- Common/DTS.Common.Import/XML/XMLParseGlobalSettings.cs
- Common/DTS.Common.Import/XML/XMLPre20ParseDASList.cs
- Common/DTS.Common.Import/XML/XMLParseCustomerDetails.cs
- Common/DTS.Common.Import/XML/XMLParseSensors.cs
- Common/DTS.Common.Import/XML/XMLParseDASList.cs
- Common/DTS.Common.Import/XML/XMLParseGroupTemplates.cs
- Common/DTS.Common.Import/XML/XMLParseCalibrations.cs
- Common/DTS.Common.Import/XML/XMLPre20ParseCalibrations.cs
- Common/DTS.Common.Import/XML/XMLParseGroups.cs
- Common/DTS.Common.Import/XML/XMLParseTestSetups.cs
generated_at: "2026-04-16T02:07:16.226597+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "87dec39982bd8179"
---
# XML Import Parsers Module Documentation
## 1. Purpose
This module provides a family of XML parsing classes responsible for deserializing versioned XML export files into the systems in-memory `ImportObject` representation during data import operations. Each concrete parser targets a specific data domain (e.g., sensors, groups, channels, test setups, calibration data, MME custom objects) and handles both direct parsing and migration logic for pre-2.0 XML formats. These parsers implement the `IParseVariant` interface, inherit from `XMLParseBase`, and coordinate with other parsers (e.g., via constructor injection) to resolve cross-references and normalize identifiers across related entities. The module serves as the core deserialization layer for backward-compatible XML imports, ensuring robust handling of schema evolution and data integrity constraints.
## 2. Public Interface
All classes inherit from `XMLParseBase` and implement the abstract method `Parse(ref ImportObject importObject)`. Below are the public classes and their key behaviors.
### `XMLParseBase`
- **Constructor**: `XMLParseBase(XmlElement root, double importedVersion, Func<bool> isCancelled = null, bool skipNormalizing = false)`
- Stores the XML root node, imported file version, cancellation token, and normalization flag.
- Initializes internal XML writer and mapping dictionaries (`_dasIdMapping`, `_groupIdMapping`, `_sensorIdMapping`, `_channelIdMapping`) for cross-entity ID normalization.
- **Protected Methods**:
- `XmlElement GetXmlElement()` Finalizes XML writing and returns the constructed `XmlElement`.
- `bool IsCancelled()` Invokes the cancellation token (defaults to `() => false`).
### `XMLParseSensors`
- **Constructor**: `XMLParseSensors(XmlElement root, double importedVersion, Func<bool> isCancelled = null, bool skipNormalizing = false)`
- **Public Methods**:
- `XmlElement ConvertSensors(IEnumerable<SensorData> sensors)` Normalizes sensor `DatabaseId`s (assigns negative IDs starting at -2), updates `_sensorIdMapping`, and writes normalized XML.
- `IEnumerable<SensorData> ParseSensors(XmlElement root)` Parses `SensorData` objects from XML nodes; skips non-`XmlElement` nodes.
### `XMLPre20ParseSensors`
- **Constructor**: `XMLPre20ParseSensors(XmlElement root, double importedVersion, XMLParseSensors xmlParseSensors, Func<bool> isCancelled = null)`
- **Public Methods**:
- `XmlElement MigrateSensors(IEnumerable<SensorData> sensors)` Migrates pre-2.0 sensor data by assigning negative `DatabaseId`s (starting at -1) and writing to XML.
- **Behavior**: Parses sensors, migrates them to the new schema, then delegates to `_xmlParseSensors.ParseSensors()` for final parsing.
### `XMLParseDASList`
- **Constructor**: `XMLParseDASList(XmlElement root, double importedVersion, Func<bool> isCancelled = null, bool skipNormalizing = false)`
- **Public Methods**:
- `List<DASHardware> ParseDASList(XmlElement root)` Parses `DASHardware` objects; filters out entries with invalid `DASType` (logs serial numbers to `invalidDAS` list, but does not report errors).
- `XmlElement ConvertDASList(List<DASHardware> dasList)` Normalizes `DASId`s (starting at -2) and updates `_dasIdMapping`.
- **Behavior**: On import, validates `DASType` enum values; skips invalid entries silently.
### `XMLPre20ParseDASList`
- **Constructor**: `XMLPre20ParseDASList(XmlElement root, double importedVersion, XMLParseDASList xmlParseDASList, Func<bool> isCancelled = null)`
- **Public Methods**:
- `List<DASHardware> ParsePre20DASList(XmlElement root)` Parses pre-2.0 `DASHardware` objects.
- `XmlElement MigratePre20DASList(List<DASHardware> dasList)` Assigns negative `DASId`s (starting at -1) and writes to XML.
- **Behavior**: Migrates pre-2.0 DAS list, then delegates to `_xmlParseDASList.ParseDASList()`.
### `XMLParseGroups`
- **Constructor**: `XMLParseGroups(XmlElement root, double importedVersion, Func<bool> isCancelled = null)`
- **Public Methods**:
- `List<IGroup> ParseGroups(XmlElement root, ref ImportObject importObject)` Parses groups, resolving channel references to imported sensors/DAS via lookup dictionaries. Handles both pre-2.0 (name-based) and post-2.0 (ID-based) group IDs in `_groupIdMapping`.
- `XmlElement ConvertGroups(List<IGroup> staticGroups)` Normalizes group `Id`s (starting at -2), DAS/sensor IDs in channels (using `_dasIdMapping`/`_sensorIdMapping`), and writes to XML. Silently sets `SensorId = 0` for deleted sensors (FB 14308).
- **Behavior**: Normalizes group and channel IDs; handles sensor deletion gracefully.
### `XMLParseGroupTemplates`
- **Constructor**: `XMLParseGroupTemplates(XmlElement root, double importedVersion, ISO.ISO13499FileDb iSO13499FileDb, Func<bool> isCancelled = null)`
- **Public Methods**:
- `IEnumerable<DataPROWin7.DataModel.TestObjectTemplate> ParseGroupTemplates(ref ImportObject importObject, XmlElement root)` Parses group templates, resolving channels from `importObject.CustomChannels()`. Enforces that referenced ISO test objects exist in `importObject.TestObjects()` (throws `NotSupportedException` if missing, FB 8790).
- **Behavior**: Requires pre-parsed custom channels and test objects; validates test object existence.
### `XMLParseTestSetups`
- **Constructor**: `XMLParseTestSetups(XmlElement root, double importedVersion, Func<bool> isCancelled = null, bool skipNormalizing = false)`
- **Public Methods**:
- `XmlElement ConvertTestTemplates(IEnumerable<TestTemplate> testTemplates, ImportObject importObject)` Normalizes group IDs, channel IDs (using `_groupIdMapping`, `_channelIdMapping`), and DAS/sensor IDs in channels. Handles missing static group IDs (sets to `null`) and deleted sensors (sets `SensorId = 0`, FB 14308).
- `IEnumerable<TestTemplate> ParseTestTemplate(ImportObject importObject, XmlElement root)` Parses test setups using lookup dictionaries for test objects, groups, customer/lab details, sensors, etc. Catches and logs parsing errors as warnings (FB 36879).
- **Behavior**: Requires pre-parsed entities (groups, sensors, etc.) to resolve references; handles missing/invalid references gracefully.
### `XMLParseCalibrations`
- **Constructor**: `XMLParseCalibrations(XmlElement root, double importedVersion, Func<bool> isCancelled = null)`
- **Public Methods**:
- `XmlElement ConvertCalibrations(IEnumerable<SensorCalibration> calibrations)` Writes normalized calibration XML.
- `IEnumerable<SensorCalibration> ParseCalibrations(XmlElement root)` Parses calibrations with version-specific logic:
- For `importedVersion >= DataPROPre20XmlVersion`: Direct parsing.
- For `importedVersion == 1.0`: Sets `AtCapacity = false` and infers `SensitivityUnits` (mV/EU, mV/V/EU, or NONE) based on calibration type.
### `XMLPre20ParseCalibrations`
- **Constructor**: `XMLPre20ParseCalibrations(XmlElement root, double importedVersion, XMLParseCalibrations xmlParseCalibrations, Func<bool> isCancelled = null)`
- **Behavior**: Parses pre-2.0 calibrations, adds them to sensors via `AddToSensor()`, then delegates normalization and re-parsing to `_xmlParseCalibrations`.
### `XMLParseMMECustom*` Parsers (e.g., `XMLParseMMECustomChannels`, `XMLParseMMECustomPositions`, etc.)
- **Common Pattern**:
- **Constructor**: `XMLParseMMECustom* (XmlElement root, double importedVersion, Func<bool> isCancelled = null)`
- **Public/Protected Methods**:
- `ParseCustom* (XmlElement root)` Parses a list of MME objects (e.g., `ISO.MMEPossibleChannels`, `ISO.MMEPositions`) from XML child nodes.
- `Parse(ref ImportObject importObject)` Adds parsed list to `importObject` via `AddCustom*()` methods.
- **Behavior**: Iterates child `XmlElement`s, calls `ReadXML()` on each, and respects cancellation. No normalization or migration logic (assumes current-version format).
### `XMLPre20ParseGroupTemplates`
- **Constructor**: `XMLPre20ParseGroupTemplates(XmlElement root, double importedVersion, XMLParseGroupTemplates xmlParseGroupTemplates, Func<bool> isCancelled = null)`
- **Behavior**: Delegates parsing to `_xmlParseGroupTemplates.ParseGroupTemplates()`; no migration logic visible.
### `XMLParseUsers`
- **Constructor**: `XMLParseUsers(XmlElement root, double importedVersion, IEnumerable<IUIItems> uiItems, Func<bool> isCancelled = null)`
- **Behavior**: Parses `User` objects using `User.ReadXML()`, passing `uiItems` for UI context.
### `XMLParseTestEngineerDetails`, `XMLParseLabDetails`, `XMLParseCustomerDetails`
- **Pattern**: Parse → Convert → Re-parse → Add to `importObject`.
- `Parse*()` Initial parse.
- `Convert*()` Writes parsed objects to XML.
- Re-parse normalized XML to ensure consistency.
- **Behavior**: All use `Convert*()` to normalize and re-parse; no ID mapping beyond XML structure.
### `XMLParseGlobalSettings`
- **Constructor**: `XMLParseGlobalSettings(XmlElement root, double importedVersion, Func<bool> isCancelled = null)`
- **Behavior**: Parses `<SettingName>` and `<SettingValue>` child elements into a `Dictionary<string, string>`.
## 3. Invariants
- **ID Normalization**: All parsers that normalize IDs (e.g., `XMLParseSensors`, `XMLParseDASList`, `XMLParseGroups`, `XMLParseTestSetups`) assign negative integer IDs starting at -1 or -2 (to avoid conflicts with database IDs). Mappings are stored in static dictionaries (`_sensorIdMapping`, `_dasIdMapping`, `_groupIdMapping`, `_channelIdMapping`) and used during cross-entity resolution.
- **Cancellation Handling**: All parsers check `IsCancelled()` before processing each node and return partial results if cancelled.
- **XML Node Validation**: All parsers skip non-`XmlElement` nodes (e.g., whitespace, comments) during iteration.
- **Version-Specific Parsing**: Calibrations and sensors have distinct parsing logic for `importedVersion == 1.0` vs. newer versions.
- **Group Template Validation**: `XMLParseGroupTemplates` enforces that referenced ISO test objects exist in `importObject.TestObjects()`; throws `NotSupportedException` if missing.
- **DAS Type Validation**: `XMLParseDASList` filters out `DASHardware` with invalid `DASType` enums but does not report errors (silently drops entries).
- **Sensor Deletion Handling**: Group and Test Setup parsers set `SensorId = 0` for deleted sensors (FB 14308) to avoid import failures.
## 4. Dependencies
### Internal Dependencies
- **Imports**:
- `DTS.Common.Import.Interfaces` (`ImportObject`, `IParseVariant`, `IImportNotification`, `ImportStatus`, `ImportError`, `ImportSeverityError`)
- `DTS.Common.Interface.*` (e.g., `GroupTemplate`, `Groups.GroupList`, `Sensors`, `Channels`)
- `DTS.SensorDB` (`SensorModel`, `SensorData`, `SensorCalibration`)
- `DataPROWin7.DataModel` (`DASHardware`, `TestObjectTemplate`)
- `ISO.*` (e.g., `MMEPossibleChannels`, `MMETestObjects`, `MMEPositions`, `TestEngineerDetails`, `CustomerDetails`, `LabratoryDetails`)
- `System.Xml` (`XmlElement`, `XmlDocument`, `XmlWriter`)
- **Shared State**:
- Static dictionaries in `XMLParseBase` (`_dasIdMapping`, `_groupIdMapping`, `_sensorIdMapping`, `_channelIdMapping`) are shared across parsers in the same import session.
- `ImportNotification` property used for status updates (settable via property).
- **Constructor Injection**:
- Pre-2.0 parsers (`XMLPre20ParseSensors`, `XMLPre20ParseDASList`, `XMLPre20ParseCalibrations`, `XMLPre20ParseGroupTemplates`) depend on their post-2.0 counterparts to perform normalization and final parsing.
### External Dependencies
- `DataPROWin7.DataModel.DASHardware.ReadXML()` Static method for pre-2.0 DAS parsing.
- `ISO.*.ReadXML()` Static methods for MME object parsing (e.g., `ISO.MMEPossibleChannels.ReadXML()`).
- `User.ReadXML()` Static method for user parsing.
- `SensorData.ReadXML()`, `SensorCalibration.ReadXML()` Static methods for sensor/calibration parsing.
- `DataPROWin7.DataModel.TestObjectTemplate.ReadXML()` Static method for group template parsing.
## 5. Gotchas
- **Silent DAS Filtering**: `XMLParseDASList.ParseDASList()` silently drops DAS entries with invalid `DASType` enums (no error logged or reported).
- **ID Mapping Side Effects**: Static ID mappings in `XMLParseBase` are shared across parsers; clearing them (e.g., in `XMLParseDASList` constructor) may affect other parsers if not coordinated.
- **Version-Specific Calibration Logic**: For `importedVersion == 1.0`, `XMLParseCalibrations` mutates calibration records (e.g., sets `AtCapacity = false`, infers `SensitivityUnits`). This logic is duplicated in `XMLPre20ParseCalibrations`.
- **FB References**: Several parsers reference internal bug IDs (e.g., FB 8790, FB 14308, FB 36879) in comments or exceptions; these are internal tracking references.
- **Test Setup Reference Resolution**: `XMLParseTestSetups.ParseTestTemplate()` uses lookup dictionaries built from `importObject` entities; if entities are not parsed in the correct order, lookups may fail (e.g., missing groups/sensors).
- **Group Template Test Object Validation**: `XMLParseGroupTemplates` throws `NotSupportedException` if a referenced ISO test object is missing; this is a hard failure.
- **XML Writer State**: `XMLParseBase` uses a shared `XmlWriter` instance per parser; reusing the same writer across multiple `Convert*()` calls without reinitialization may cause issues (though current code creates a new `_doc` per instance).
- **Pre-2.0 Migration Assumptions**: `XMLPre20Parse*` parsers assume the target parser (`_xmlParse*`) handles normalization; no validation that the target parser is compatible with the migrated XML structure.
- **Group ID Mapping Ambiguity**: `XMLParseGroups` uses `_groupIdMapping` with keys as `g.Id.ToString()` (post-2.0) or `g.Name` (pre-2.0); this dual-key approach may cause issues if group names collide with numeric IDs.
- **Channel ID Normalization**: `XMLParseTestSetups` normalizes channel IDs *after* normalizing group IDs; order of normalization matters for cross-references.