From 2cbe0d760cb70619dd3d09adfbc338e7710d3a41 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 16:43:21 +0700 Subject: [PATCH 01/13] Add temporary P1 workspace inspector --- scripts/inspect-p1-workspaces.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 scripts/inspect-p1-workspaces.py diff --git a/scripts/inspect-p1-workspaces.py b/scripts/inspect-p1-workspaces.py new file mode 100644 index 000000000..4fe1c641b --- /dev/null +++ b/scripts/inspect-p1-workspaces.py @@ -0,0 +1,19 @@ +from pathlib import Path + +path = Path('MainWindow.xaml') +text = path.read_text(encoding='utf-8') +markers = [ + ('EVENT', '', ''), + ('ALARM', '', ''), + ('GOOSE', '', ''), + ('DIAGNOSTICS', '', ''), +] +for label, start, end in markers: + a = text.find(start) + b = text.find(end, a + len(start)) + if a < 0 or b < 0: + raise SystemExit(f'{label}: markers not found') + section = text[a:b] + print(f'===== {label} ({section.count(chr(10))+1} lines) =====') + print(section) + print(f'===== END {label} =====') From 6db7d44307e3e45b731a048db35c3e0e1f10117a Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 16:43:33 +0700 Subject: [PATCH 02/13] Run temporary P1 workspace inspection --- .github/workflows/temp-inspect-p1-workspaces.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/temp-inspect-p1-workspaces.yml diff --git a/.github/workflows/temp-inspect-p1-workspaces.yml b/.github/workflows/temp-inspect-p1-workspaces.yml new file mode 100644 index 000000000..c154252a0 --- /dev/null +++ b/.github/workflows/temp-inspect-p1-workspaces.yml @@ -0,0 +1,16 @@ +name: Temporary P1 workspace inspection + +on: + push: + branches: + - polish/p1-workspace-noise-cleanup + +jobs: + inspect: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: polish/p1-workspace-noise-cleanup + - run: python scripts/inspect-p1-workspaces.py From 31feb14dd9fa58cf32f31493813638217e8d6ac8 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 16:47:22 +0700 Subject: [PATCH 03/13] Add presentation-only IEC quality tones for P1 --- Models/Iec61850ValueStatePresentation.cs | 52 ++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Models/Iec61850ValueStatePresentation.cs b/Models/Iec61850ValueStatePresentation.cs index 49e559ce8..5ddf59cf9 100644 --- a/Models/Iec61850ValueStatePresentation.cs +++ b/Models/Iec61850ValueStatePresentation.cs @@ -63,3 +63,55 @@ private static bool IsBooleanType(string? dataType) type.Contains("boolean", StringComparison.Ordinal); } } + +/// +/// Presentation-only attention tone for IEC 61850 quality text. Process state remains +/// independent: this classifier only decides how strongly the Quality column should +/// call for operator attention. +/// +public static class Iec61850QualityPresentation +{ + public const string Good = "Good"; + public const string Attention = "Attention"; + public const string Bad = "Bad"; + public const string Unknown = "Unknown"; + + public static string Classify(string? quality) + { + var text = (quality ?? string.Empty).Trim(); + if (text.Length == 0 || text == "-" || text.Equals("Unknown", StringComparison.OrdinalIgnoreCase)) + return Unknown; + + var normalized = text.ToLowerInvariant(); + if (ContainsAny(normalized, "invalid", "bad", "failure", "failed")) + return Bad; + + if (ContainsAny( + normalized, + "questionable", + "olddata", + "old data", + "substituted", + "test", + "operatorblocked", + "operator blocked", + "overflow", + "outofrange", + "out of range", + "inaccurate", + "oscillatory")) + { + return Attention; + } + + if (normalized.Contains("good", StringComparison.Ordinal)) + return Good; + + // A non-empty quality string that is not explicitly proven Good still deserves + // a contained amber cue rather than being silently presented as healthy. + return Attention; + } + + private static bool ContainsAny(string source, params string[] needles) + => needles.Any(needle => source.Contains(needle, StringComparison.Ordinal)); +} From 5c62381d1fa7f8093ac4a03c2ea1c9e1d0df7904 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 17 Aug 2026 16:48:35 +0700 Subject: [PATCH 04/13] Add temporary deterministic P1 patch --- scripts/apply-p1-workspace-noise.py | 384 ++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 scripts/apply-p1-workspace-noise.py diff --git a/scripts/apply-p1-workspace-noise.py b/scripts/apply-p1-workspace-noise.py new file mode 100644 index 000000000..97edb37a9 --- /dev/null +++ b/scripts/apply-p1-workspace-noise.py @@ -0,0 +1,384 @@ +from pathlib import Path + +MAIN = Path('MainWindow.xaml') +GOOSE = Path('MainWindow.GooseSubscriber.cs') +MODELS = Path('Models/MonitorModels.cs') + +main = MAIN.read_text(encoding='utf-8') +goose_code = GOOSE.read_text(encoding='utf-8') +models = MODELS.read_text(encoding='utf-8') + + +def replace_once(text: str, old: str, new: str, label: str) -> str: + count = text.count(old) + if count != 1: + raise SystemExit(f'{label}: expected one match, found {count}') + return text.replace(old, new, 1) + + +def replace_section(text: str, start: str, end: str, replacement: str, label: str) -> str: + a = text.find(start) + if a < 0: + raise SystemExit(f'{label}: start marker missing') + b = text.find(end, a + len(start)) + if b < 0: + raise SystemExit(f'{label}: end marker missing') + return text[:a] + replacement + text[b:] + +# --- MainWindow resources: contained quality and diagnostic severity cues. --- +if 'x:Key="EventQualityBadgeTemplate"' not in main: + resources = r''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +''' + main = replace_once(main, ' ', resources + ' ', 'window presentation templates') + +# --- Event Log: keep process state separate; contain attention inside Quality. --- +event_block = r''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ''' +goose = goose[:top_start] + goose_top + goose[top_end:] +goose = replace_once( + goose, + '', + '', + 'GOOSE DataSet progressive card') +main = main[:goose_start] + goose + main[goose_end:] + +# --- Diagnostics: level badge + left rail only; never flood every cell with severity fill. --- +diag_block = r''' + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ''' -goose = goose[:top_start] + goose_top + goose[top_end:] -goose = replace_once( - goose, - '', - '', - 'GOOSE DataSet progressive card') -main = main[:goose_start] + goose + main[goose_end:] - -# --- Diagnostics: level badge + left rail only; never flood every cell with severity fill. --- -diag_block = r''' - - - - - - - - - - -