Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
914cc30
discovery: promote verified smart route into source
masarray Sep 19, 2026
dfbabf0
discovery: enter shared IED actions workflow after model build
masarray Sep 19, 2026
7e5bb3a
model: share canonical DataSet directory projection across sources
masarray Sep 19, 2026
043a710
reporting: reuse canonical DataSet order after discovery
masarray Sep 19, 2026
03e28f5
reporting: keep model authority consistent through projection
masarray Sep 19, 2026
444a484
ux: derive static DataSet action from canonical model capability
masarray Sep 19, 2026
47c4988
ux: enable static DataSet action only when model contains DataSets
masarray Sep 19, 2026
8c95fd7
tests: lock discovery to opened-model static workflow parity
masarray Sep 19, 2026
c953633
tests: lock report planning to completed discovery authority
masarray Sep 19, 2026
bf42006
discovery: expose model actions when static DataSets exist
masarray Sep 19, 2026
1f64117
tests: keep DataSet-only discovery eligible for shared actions
masarray Sep 19, 2026
b9238b5
discovery: keep smart route evidence-gated until field acceptance
masarray Sep 20, 2026
a447463
tests: lock canonical static DataSet order without reread
masarray Sep 20, 2026
316cc5a
tests: keep smart discovery promotion evidence-gated
masarray Sep 20, 2026
afd9f21
tests: support pre and post smart-route build transform
masarray Sep 20, 2026
47b7f00
ui: restore IEC process-state text for report values
masarray Sep 20, 2026
73dd2e7
reporting: normalize display text without changing report evidence
masarray Sep 20, 2026
ecd02c3
tests: lock report value operator text and context
masarray Sep 20, 2026
4999b5a
ui: rebuild report state formatter from clean P0 baseline
masarray Sep 20, 2026
70bcd17
ui: classify analog boolean and position value families
masarray Sep 20, 2026
788b184
ui: expose semantic value visual kind to process grids
masarray Sep 20, 2026
a974b75
ui: visually separate analog boolean and position values
masarray Sep 20, 2026
d461b10
tests: lock semantic value family visual classification
masarray Sep 20, 2026
36e7517
ui: keep legacy value tone compatible with formatted booleans
masarray Sep 20, 2026
8433aa9
tests: cover formatted booleans and family badge markers
masarray Sep 20, 2026
947c9ae
ui: keep generic enum on off neutral without boolean authority
masarray Sep 20, 2026
8dd5d62
static dataset: resolve exact structured primary leaves
masarray Sep 20, 2026
5bfe7ba
static dataset: converge structured discovery members on exact schema…
masarray Sep 20, 2026
238fde9
units: add bounded authoritative static dataset enrichment
masarray Sep 20, 2026
5728c56
runtime: enrich selected units before static report start
masarray Sep 20, 2026
ad0d52a
workflow: verify units before static report monitoring
masarray Sep 20, 2026
e1e8332
scl: allow canonical save without interrupting active monitoring
masarray Sep 20, 2026
2c7976b
ui: derive value type badge from declared IEC data type
masarray Sep 20, 2026
d5f00f0
ui: expose exact IEC value type tokens to live surfaces
masarray Sep 20, 2026
8f2e37e
ui: show declared IEC value types instead of analog guesses
masarray Sep 20, 2026
3e65529
tests: lock metadata-only value type badges
masarray Sep 20, 2026
dab16a0
tests: preserve structured dataset members as exact runtime leaves
masarray Sep 20, 2026
0fc6be1
tests: lock discovery static parity save and unit contracts
masarray Sep 20, 2026
91cef17
static dataset: repair exact schema runtime binding source
masarray Sep 20, 2026
08ba4f2
runtime: route static unit enrichment through UI facade
masarray Sep 20, 2026
831db44
tests: lock exact structured member outcome not provenance wording
masarray Sep 20, 2026
9dde9a7
units: accept exact MMS floating-point type spelling
masarray Sep 20, 2026
0692eb9
tests: lock exact MMS floating point unit enrichment
masarray Sep 20, 2026
01f40c1
tests: cover native MMS type spellings in value badges
masarray Sep 20, 2026
09dede9
units: unify verified metadata before static reporting
masarray Sep 20, 2026
9638517
units: verify static SCL measurements before monitor start
masarray Sep 20, 2026
76d7741
tests: keep static unit enrichment source-neutral
masarray Sep 20, 2026
47ee31f
tests: lock twelve structured analog rows for discovery parity
masarray Sep 20, 2026
da13c73
fix: unify discovery state semantics and FAT evidence presentation
masarray Sep 20, 2026
0d0b920
tests: align FAT preview expectations with semantic display
masarray Sep 20, 2026
3005ad5
test: freeze P0.7 physical release-candidate baseline
masarray Sep 20, 2026
6d6c5f6
docs: keep P0.7 release lock source-clean
masarray Sep 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions MainWindow.SclQuickActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ internal async Task OpenIedWorkspaceActionsAsync(Iec61850MonitorDevice device)
return;
}

// Unit metadata belongs to the same source-neutral Static DataSet workflow.
// Resolve only selected measurement owners on the active MMS session, before
// any RCB is armed, so discovery and opened-SCL paths expose the same verified
// engineering-unit behavior without disturbing active reporting.
SetStatus($"{device.Name}: verifying engineering units for selected Static DataSet measurements…");
await _runtime.EnrichSelectedStaticDataSetUnitsAsync(
device,
_applicationCancellation.Token);
device.RefreshComputed();

if (!device.IsMonitoring)
await StartDeviceMonitorAsync(device);
return;
Expand Down
121 changes: 100 additions & 21 deletions MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,125 @@
TextOptions.TextHintingMode="Fixed"
Icon="Assets/app-icon.ico" Closing="Window_Closing" PreviewKeyDown="MainWindow_PreviewKeyDown">
<Window.Resources>
<!-- Process state is intentionally separate from health/severity. Active is
ARSAS blue, inactive is slate, abnormal/intermediate is amber. Red/green
remain reserved for faults, bad quality, availability and success. -->
<!-- Process-value family is presentation-only and remains separate from alarm
severity/quality. A/B/P markers make analog, Boolean and position values
scannable without relying on color alone. Normal state families avoid red/green
so availability, success, faults and bad quality keep their existing semantics. -->
<DataTemplate x:Key="ProcessValueBadgeTemplate">
<Border x:Name="ProcessValueBadge"
CornerRadius="9" Padding="8,3" MinWidth="62"
CornerRadius="9" Padding="6,3" MinWidth="62"
HorizontalAlignment="Left"
Background="#F6F8FB" BorderBrush="#DCE3EC" BorderThickness="1">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Ellipse x:Name="ProcessValueDot" Width="7" Height="7" Margin="0,0,6,0"
Fill="#94A3B8" Stroke="#94A3B8" StrokeThickness="1.2"
VerticalAlignment="Center"/>
<Border x:Name="ProcessValueKindBadge"
MinWidth="20" Height="17" CornerRadius="5" Padding="3,0"
Margin="0,0,6,0" Visibility="Collapsed"
Background="#EEF2F6" BorderBrush="#D6DEE8" BorderThickness="1"
ToolTip="{Binding IecDataType}">
<TextBlock x:Name="ProcessValueKindText"
Text="{Binding ValueTypeToken}"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="8.2" FontWeight="Bold" Foreground="#65758A"/>
</Border>
<TextBlock x:Name="ProcessValueText"
Text="{Binding DisplayValue}" ToolTip="{Binding DisplayValue}"
FontSize="11.8" FontWeight="SemiBold" Foreground="#344054"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ValueTone}" Value="Active">
<!-- Type markers are metadata-only: F/I/U/B/E/DP come from the declared
IEC/MMS data type. No value-shape or name heuristic creates a badge. -->
<DataTrigger Binding="{Binding ValueTypeToken}" Value="F">
<Setter TargetName="ProcessValueKindBadge" Property="Visibility" Value="Visible"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#EEF4F8"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#CCD9E3"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="#536D7C"/>
</DataTrigger>
<DataTrigger Binding="{Binding ValueTypeToken}" Value="I">
<Setter TargetName="ProcessValueKindBadge" Property="Visibility" Value="Visible"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#EEF7F3"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#C9DED5"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="#4E7567"/>
</DataTrigger>
<DataTrigger Binding="{Binding ValueTypeToken}" Value="U">
<Setter TargetName="ProcessValueKindBadge" Property="Visibility" Value="Visible"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#F1F7F0"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#D2E1CE"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="#607B58"/>
</DataTrigger>
<DataTrigger Binding="{Binding ValueTypeToken}" Value="E">
<Setter TargetName="ProcessValueKindBadge" Property="Visibility" Value="Visible"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#F5F3F8"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#DDD7E7"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="#6C6480"/>
</DataTrigger>
<DataTrigger Binding="{Binding ValueTypeToken}" Value="B">
<Setter TargetName="ProcessValueKindBadge" Property="Visibility" Value="Visible"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#F3F0FF"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#D8D2F5"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="#6557B5"/>
</DataTrigger>
<DataTrigger Binding="{Binding ValueTypeToken}" Value="DP">
<Setter TargetName="ProcessValueKindBadge" Property="Visibility" Value="Visible"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#EEF7FC"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#C6DFEC"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="#347795"/>
</DataTrigger>

<!-- Process state changes tone only; it never changes the type token. -->
<DataTrigger Binding="{Binding ValueVisualKind}" Value="BooleanTrue">
<Setter TargetName="ProcessValueBadge" Property="Background" Value="#F1EFFF"/>
<Setter TargetName="ProcessValueBadge" Property="BorderBrush" Value="#D8D2F5"/>
<Setter TargetName="ProcessValueText" Property="Foreground" Value="#5A4DA7"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#7567D8"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#7567D8"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="White"/>
</DataTrigger>

<DataTrigger Binding="{Binding ValueVisualKind}" Value="BooleanFalse">
<Setter TargetName="ProcessValueBadge" Property="Background" Value="#F8F7FC"/>
<Setter TargetName="ProcessValueBadge" Property="BorderBrush" Value="#E2DFED"/>
<Setter TargetName="ProcessValueText" Property="Foreground" Value="#6C657D"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="Transparent"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#978DAA"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="#6F6681"/>
</DataTrigger>

<DataTrigger Binding="{Binding ValueVisualKind}" Value="PositionOpen">
<Setter TargetName="ProcessValueBadge" Property="Background" Value="#ECF8FF"/>
<Setter TargetName="ProcessValueBadge" Property="BorderBrush" Value="#B9E2F4"/>
<Setter TargetName="ProcessValueText" Property="Foreground" Value="#1F6F8F"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="Transparent"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#3B9FC5"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="#247A9B"/>
</DataTrigger>

<DataTrigger Binding="{Binding ValueVisualKind}" Value="PositionClose">
<Setter TargetName="ProcessValueBadge" Property="Background" Value="#EAF4FF"/>
<Setter TargetName="ProcessValueBadge" Property="BorderBrush" Value="#B8D8F5"/>
<Setter TargetName="ProcessValueText" Property="Foreground" Value="#245F9E"/>
<Setter TargetName="ProcessValueDot" Property="Fill" Value="#2F80ED"/>
<Setter TargetName="ProcessValueDot" Property="Stroke" Value="#2F80ED"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#2F80ED"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#2F80ED"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="White"/>
</DataTrigger>
<DataTrigger Binding="{Binding ValueTone}" Value="Inactive">
<Setter TargetName="ProcessValueBadge" Property="Background" Value="#F3F6F9"/>
<Setter TargetName="ProcessValueBadge" Property="BorderBrush" Value="#D5DEE8"/>
<Setter TargetName="ProcessValueText" Property="Foreground" Value="#617286"/>
<Setter TargetName="ProcessValueDot" Property="Fill" Value="Transparent"/>
<Setter TargetName="ProcessValueDot" Property="Stroke" Value="#7C8FA3"/>
<Setter TargetName="ProcessValueDot" Property="StrokeThickness" Value="1.7"/>
</DataTrigger>
<DataTrigger Binding="{Binding ValueTone}" Value="Abnormal">

<DataTrigger Binding="{Binding ValueVisualKind}" Value="PositionIntermediate">
<Setter TargetName="ProcessValueBadge" Property="Background" Value="#FFF8E6"/>
<Setter TargetName="ProcessValueBadge" Property="BorderBrush" Value="#F2D28A"/>
<Setter TargetName="ProcessValueText" Property="Foreground" Value="#946200"/>
<Setter TargetName="ProcessValueDot" Property="Fill" Value="#F59E0B"/>
<Setter TargetName="ProcessValueDot" Property="Stroke" Value="#F59E0B"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#F59E0B"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#F59E0B"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="White"/>
</DataTrigger>

<DataTrigger Binding="{Binding ValueVisualKind}" Value="PositionBad">
<Setter TargetName="ProcessValueBadge" Property="Background" Value="#FFF2E8"/>
<Setter TargetName="ProcessValueBadge" Property="BorderBrush" Value="#F3BE91"/>
<Setter TargetName="ProcessValueText" Property="Foreground" Value="#A14B0A"/>
<Setter TargetName="ProcessValueKindBadge" Property="Background" Value="#E8791B"/>
<Setter TargetName="ProcessValueKindBadge" Property="BorderBrush" Value="#E8791B"/>
<Setter TargetName="ProcessValueKindText" Property="Foreground" Value="White"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Expand Down
46 changes: 37 additions & 9 deletions MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,13 @@ private async void OpenScl_Click(object sender, RoutedEventArgs e)
var connected = firstImported.IsConnected ||
await ConnectUsingSavedModelAsync(firstImported);
if (connected && !firstImported.IsMonitoring)
{
await _runtime.EnrichSelectedStaticDataSetUnitsAsync(
firstImported,
_applicationCancellation.Token);
firstImported.RefreshComputed();
await StartDeviceMonitorAsync(firstImported);
}
}
}
else
Expand Down Expand Up @@ -763,12 +769,20 @@ private async Task<bool> ConnectAndConfigureDeviceAsync(
device.IsBusy = false;
device.RefreshComputed();

if (openWizard && device.SignalCount > 0)
var discoveredDataSetCount = device.LiveDiscoveryModel?.DataSets.Count ?? 0;
if (openWizard && (device.SignalCount > 0 || discoveredDataSetCount > 0))
{
if ((selectDevice || ReferenceEquals(SelectedDevice, device)) && !_signalSelectionWizardOpen)
await OpenSignalSelectionWizardAsync(device, restoredCount);
{
// Discovery and Open SCL converge here. Once a canonical model exists,
// both sources present the same task-first IED Actions workflow so Static
// DataSet acquisition is source-neutral.
await OpenIedWorkspaceActionsAsync(device);
}
else
{
SetStatus($"{device.Name}: discovery complete. Use the edit icon on its IED card to review {restoredCount} restored selection(s).");
}
}
return true;
}
Expand Down Expand Up @@ -1559,13 +1573,27 @@ private async void IedSaveScl_Click(object sender, RoutedEventArgs e)
{
if (device.SclWorkspace == null && device.IsConnected)
{
SetStatus($"{device.Name}: enriching Save SCL with bounded live FC-root values…");
var instanceEvidence = await _runtime
.EnrichCanonicalForSclSaveAsync(device, _applicationCancellation.Token);
AddLog(
"INFO",
"SCL Export",
$"{device.Name}: save-time enrichment completed with {instanceEvidence:N0} exact instance-value leaf/leaves. Fast discovery remained unchanged.");
if (device.IsMonitoring)
{
// Save is a snapshot/export operation, not an acquisition-mode change.
// Never tear down an armed report session merely to collect optional
// instance <Val> evidence. The canonical discovery/model already bound
// to this accepted MMS association is sufficient for interoperable SCL.
AddLog(
"INFO",
"SCL Export",
$"{device.Name}: monitoring remains active; Save SCL uses the current canonical model and skips optional save-time value enrichment.");
}
else
{
SetStatus($"{device.Name}: enriching Save SCL with bounded live FC-root values…");
var instanceEvidence = await _runtime
.EnrichCanonicalForSclSaveAsync(device, _applicationCancellation.Token);
AddLog(
"INFO",
"SCL Export",
$"{device.Name}: save-time enrichment completed with {instanceEvidence:N0} exact instance-value leaf/leaves. Fast discovery remained unchanged.");
}
}

if (device.SclWorkspace != null &&
Expand Down
Loading
Loading