Add CompanyName and Department to the results CSV
Add-PersonaRunResult now optionally accepts the normalized UserRecord and pulls CompanyName/Department from it for the results.csv row, via TryGetValue rather than the Properties dictionary indexer so a record built without those keys doesn't throw. Kept off the PersonaDecisionResult/audit contract on purpose - this is CSV-only, not a widening of what gets logged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,7 +57,7 @@ property-only rules.
|
||||
| --- | --- | --- | --- |
|
||||
| `destination` | no | `both` | `file`, `stream`, `both`, or `none`. `stream` writes records to the PowerShell Information stream. |
|
||||
| `path` | no | `<current-directory>/logs/persona-engine-audit.ndjson` | NDJSON output file. One record per line. |
|
||||
| `resultsFileName` | no | `results.csv` | Per-account results CSV, written next to `path`'s directory. Lists `AccountObjectId`, `UserPrincipalName`, `PersonaStatus` for every account processed so far. Overwritten on every summary, interim and final. |
|
||||
| `resultsFileName` | no | `results.csv` | Per-account results CSV, written next to `path`'s directory. Lists `AccountObjectId`, `UserPrincipalName`, `PersonaStatus`, `CompanyName`, `Department` for every account processed so far. Overwritten on every summary, interim and final. |
|
||||
| `traceConditionValues` | no | `false` | Writes evaluated attribute values into audit records. |
|
||||
| `acknowledgeConditionTracing` | no | `false` | **Required whenever `traceConditionValues` is true** (VR-003). |
|
||||
|
||||
|
||||
+4
-3
@@ -42,9 +42,10 @@ or the warning that matters is buried in the noise it generates.
|
||||
|
||||
Alongside the NDJSON audit log, every summary — interim and final — (re)writes a plain CSV listing
|
||||
every account processed so far: `AccountObjectId`, `UserPrincipalName`, `PersonaStatus` (the assigned
|
||||
persona for `Matched` accounts, otherwise `Unclassified` or `EvaluationError`). It is overwritten in
|
||||
full each time, not appended, so it always reflects the whole run to that point rather than only the
|
||||
accounts since the last summary.
|
||||
persona for `Matched` accounts, otherwise `Unclassified` or `EvaluationError`), `CompanyName`, and
|
||||
`Department` (as retrieved from the directory, blank when absent). It is overwritten in full each
|
||||
time, not appended, so it always reflects the whole run to that point rather than only the accounts
|
||||
since the last summary.
|
||||
|
||||
It is written next to the audit log — same directory as `logging.path` — under `logging.resultsFileName`
|
||||
(default `results.csv`). Export failure never ends a run, for the same reason a sink failure doesn't:
|
||||
|
||||
@@ -47,8 +47,8 @@ configuration look identical if zero-match rules are omitted, and that distincti
|
||||
you are looking for. Its header shows elapsed wall-clock time since the run started.
|
||||
|
||||
Each summary also (re)writes `logging.resultsFileName` (default `results.csv`, next to the audit log)
|
||||
with one row per account processed so far — Object ID, UPN, and assigned persona/status — for an
|
||||
operator who wants the current population breakdown without parsing NDJSON.
|
||||
with one row per account processed so far — Object ID, UPN, assigned persona/status, company name,
|
||||
and department — for an operator who wants the current population breakdown without parsing NDJSON.
|
||||
|
||||
## Exit codes
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ Failing any of these yields `Unchanged`, `WouldUpdate`, or `Skipped` — never a
|
||||
(FR-020).
|
||||
- **Every summary, interim and final**: `logging.resultsFileName` (default `results.csv`, written
|
||||
next to the audit log) is overwritten with one row per account processed so far — Account Object
|
||||
ID, UPN, and the assigned persona or outcome status.
|
||||
ID, UPN, the assigned persona or outcome status, company name, and department.
|
||||
- **Reconciliation** at every summary: `Processed = Matched + Unclassified + EvaluationError`
|
||||
(FR-021). A mismatch is logged as an engine defect, at `Error` severity.
|
||||
- **Audit records**: see [audit-record.md](audit-record.md).
|
||||
|
||||
@@ -196,7 +196,7 @@ function Invoke-PersonaEngineRun {
|
||||
|
||||
Write-UserPersonaResult -Result $result
|
||||
|
||||
Add-PersonaRunResult -Counters $counters -Result $result
|
||||
Add-PersonaRunResult -Counters $counters -Result $result -UserRecord $record
|
||||
|
||||
New-PersonaAuditRecord -Context $Context -RecordType 'UserEvent' -Result $result `
|
||||
-PreviousValue $previousValue -IncludeTrace:$Tracing |
|
||||
|
||||
@@ -96,11 +96,18 @@ function Add-PersonaRunResult {
|
||||
|
||||
.PARAMETER Result
|
||||
A PersonaDecisionResult with both Outcome and Action populated.
|
||||
|
||||
.PARAMETER UserRecord
|
||||
The normalized UserRecord the decision was made from. Optional, and used
|
||||
only to carry CompanyName/Department onto the results CSV row - it is never
|
||||
written to counters or audit records, so this does not widen what a
|
||||
PersonaDecisionResult itself carries.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)] [object] $Counters,
|
||||
[Parameter(Mandatory)] [object] $Result
|
||||
[Parameter(Mandatory)] [object] $Result,
|
||||
[object] $UserRecord
|
||||
)
|
||||
|
||||
$Counters.Processed++
|
||||
@@ -124,11 +131,24 @@ function Add-PersonaRunResult {
|
||||
'Skipped' { $Counters.Skipped++ }
|
||||
}
|
||||
|
||||
# TryGetValue rather than the indexer: Properties is a case-insensitive
|
||||
# Dictionary, whose indexer throws on a missing key rather than returning
|
||||
# $null. CompanyName/Department are always present when UserRecord comes from
|
||||
# ConvertTo-PersonaUserRecord, but this stays safe for any other caller too.
|
||||
$companyName = $null
|
||||
$department = $null
|
||||
if ($UserRecord) {
|
||||
$null = $UserRecord.Properties.TryGetValue('CompanyName', [ref]$companyName)
|
||||
$null = $UserRecord.Properties.TryGetValue('Department', [ref]$department)
|
||||
}
|
||||
|
||||
# Matched carries the assigned persona; Unclassified/EvaluationError carry
|
||||
# their outcome name, since CalculatedPersona is 'Unclassified' or $null there.
|
||||
$Counters.Results.Add([pscustomobject]@{
|
||||
AccountObjectId = [string]$Result.AccountObjectId
|
||||
UserPrincipalName = [string]$Result.UserPrincipalName
|
||||
PersonaStatus = [string]$Result.Outcome -eq 'Matched' ? [string]$Result.CalculatedPersona : [string]$Result.Outcome
|
||||
CompanyName = [string]$companyName
|
||||
Department = [string]$department
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user