Files
personaEngine2/src/Presentation/Write-PersonaSummary.ps1
T
dave 5f125c34f2 Add elapsed run time, per-account results CSV, and a default config path
Summaries now show elapsed wall-clock time since the run started, and every
summary (interim and final) overwrites a results.csv (Object ID, UPN,
persona/status) next to the audit log, so an operator has a plain export
without parsing NDJSON. ConfigPath also now defaults to
./config/persona-engine.json instead of requiring -ConfigPath every run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 22:10:24 -04:00

99 lines
4.1 KiB
PowerShell

function Write-PersonaSummary {
<#
.SYNOPSIS
Renders the rule-match table, outcome totals, and reconciliation result
(FR-019, FR-020, FR-021).
.DESCRIPTION
Emitted every summaryInterval users and once at completion.
Every business rule appears, including disabled rules and rules with zero
matches. A rule that never fired and a rule that is not in the configuration
look identical if zero-match rules are omitted, and the difference is exactly
what an operator investigating "why did nobody get classified as Tier0" needs
to see.
Reconciliation is displayed on every summary, not only when it fails. A check
that is only visible when broken gives an operator no reason to believe it
ran at all.
.PARAMETER Counters
The run counter set.
.PARAMETER SummaryType
Interim or Final. Final is emitted regardless of interval, including when
the interval is 0 (FR-020).
.PARAMETER Mode
Preview or Enforce, shown in the header so a screenshot of a summary is
self-describing.
.PARAMETER StartedUtc
Run start timestamp. When supplied, the header shows elapsed wall-clock
time since the run began. Optional so callers that only care about counts
are not forced to thread a clock through.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[object] $Counters,
[ValidateSet('Interim', 'Final')]
[string] $SummaryType = 'Interim',
[ValidateSet('Preview', 'Enforce')]
[string] $Mode = 'Preview',
[Nullable[datetime]] $StartedUtc
)
$reconciled = Test-PersonaReconciliation -Counters $Counters
# $StartedUtc arrives here already unwrapped to a plain [datetime] - PowerShell
# collapses [Nullable[datetime]] to DateTime (or $null) at the call boundary, so
# a null check is used rather than .Value / .HasValue.
$elapsed = ($null -ne $StartedUtc) ? ('{0:hh\:mm\:ss}' -f ([DateTime]::UtcNow - $StartedUtc)) : $null
Write-Host ''
Write-Host ('=' * 100) -ForegroundColor DarkGray
Write-Host (
$elapsed `
? ("{0} summary - mode: {1} - elapsed: {2} - processed: {3}" -f $SummaryType, $Mode, $elapsed, $Counters.Processed) `
: ("{0} summary - mode: {1} - processed: {2}" -f $SummaryType, $Mode, $Counters.Processed)
) -ForegroundColor Cyan
Write-Host ('=' * 100) -ForegroundColor DarkGray
Write-Host ('{0,-28} {1,-40} {2,-9} {3,10} {4,8}' -f 'Rule ID', 'Name', 'Priority', 'Enabled', 'Matches') -ForegroundColor DarkGray
foreach ($entry in $Counters.RuleCounts) {
# A disabled rule is dimmed rather than hidden: it is part of the
# configuration and its absence from the output would read as a deletion.
$colour = if (-not $entry.Enabled) { 'DarkGray' } elseif ($entry.Matches -gt 0) { 'Green' } else { 'Gray' }
Write-Host ('{0,-28} {1,-40} {2,-9} {3,10} {4,8}' -f
$entry.RuleId,
($entry.Name.Length -gt 40 ? $entry.Name.Substring(0, 37) + '...' : $entry.Name),
$entry.Priority,
$entry.Enabled,
$entry.Matches) -ForegroundColor $colour
}
Write-Host ''
Write-Host ('Outcomes Matched: {0} Unclassified: {1} EvaluationError: {2}' -f
$Counters.Matched, $Counters.Unclassified, $Counters.EvaluationError)
Write-Host ('Actions Unchanged: {0} WouldUpdate: {1} Updated: {2} UpdateFailed: {3} Skipped: {4}' -f
$Counters.Unchanged, $Counters.WouldUpdate, $Counters.Updated, $Counters.UpdateFailed, $Counters.Skipped)
if ($reconciled) {
Write-Host ('Reconciliation PASS {0} = {1} + {2} + {3}' -f
$Counters.Processed, $Counters.Matched, $Counters.Unclassified, $Counters.EvaluationError) -ForegroundColor Green
}
else {
Write-Host ('Reconciliation FAIL {0} != {1} + {2} + {3} - this is an engine defect (FR-021)' -f
$Counters.Processed, $Counters.Matched, $Counters.Unclassified, $Counters.EvaluationError) -ForegroundColor Red
}
Write-Host ('=' * 100) -ForegroundColor DarkGray
Write-Host ''
}