Public/Set-RaidinessIntake.ps1
|
# psrunner-lint allow: New-Item — creates the local data directory the answers are stored in; never a tenant object function Set-RaidinessIntake { <# .SYNOPSIS Stores intake answers with a run, so the checks that need them can be answered instead of reported as "not measured". .DESCRIPTION Takes a filled-in questionnaire (JSON or CSV from Get-RaidinessIntake, or a plain {key: answer} object), checks the answers against the questions, and writes them into the run's data folder. The assessment picks the file up like any other evidence, so the next Export-RaidinessReport already reflects it. -Interactive walks the questions in the console instead: Enter keeps what is there, "?" shows the explanation, "s" skips the rest of the section. Answering nothing is always allowed -- an unanswered question leaves its check "not measured", which is the honest result. An answer that does not fit its question is refused, with the allowed values named. A key the questionnaire does not know is a warning, not an error, so an older file still loads against a newer module. .PARAMETER RunPath The run folder to store the answers in (from Invoke-Raidiness -Path). .PARAMETER Path Store the answers in this data folder instead of a run folder. .PARAMETER FromFile The filled-in questionnaire to read. .PARAMETER Answer Answers as a hashtable, merged over anything read from -FromFile. .PARAMETER Interactive Ask the questions in the console. .PARAMETER Section Only these sections, for -Interactive. .PARAMETER PassThru Return the stored answers. .EXAMPLE Set-RaidinessIntake -RunPath ./raidiness/2026-09-02-1431 -Interactive .EXAMPLE Set-RaidinessIntake -RunPath ./raidiness/2026-09-02-1431 -Answer @{ 'choice.dataResidency' = 'eudb' 'pilot.size' = 25 } #> [CmdletBinding(DefaultParameterSetName = 'RunFolder', SupportsShouldProcess = $true)] [OutputType([pscustomobject])] param( [Parameter(ParameterSetName = 'RunFolder', Mandatory = $true)] [string] $RunPath, [Parameter(ParameterSetName = 'DataFolder', Mandatory = $true)] [string] $Path, [string] $FromFile, [hashtable] $Answer, [switch] $Interactive, [string[]] $Section, [switch] $PassThru ) $ErrorActionPreference = 'Stop' $dataPath = if ($PSCmdlet.ParameterSetName -eq 'RunFolder') { if (-not (Test-Path -Path $RunPath)) { throw "No run folder at $RunPath." } Join-Path (Resolve-Path -Path $RunPath).Path 'data' } else { $Path } if (-not $FromFile -and -not $Answer -and -not $Interactive) { throw 'Give -FromFile, -Answer or -Interactive; there is nothing to store otherwise.' } $catalog = Read-RaidinessIntakeCatalog $byKey = @{} foreach ($question in $catalog.Questions) { $byKey[$question.Key] = $question foreach ($alias in $question.Aliases) { $byKey[$alias] = $question } } # Whatever is already stored is the starting point, so a second run adds to # the answers rather than replacing them. $answers = @{} $stored = Join-Path $dataPath 'raidiness-intake.json' if (Test-Path -Path $stored) { foreach ($pair in (Read-RaidinessIntakeAnswer -Path $stored).GetEnumerator()) { $answers[$pair.Key] = $pair.Value } } if ($FromFile) { foreach ($pair in (Read-RaidinessIntakeAnswer -Path $FromFile).GetEnumerator()) { $answers[$pair.Key] = $pair.Value } } if ($Answer) { foreach ($key in $Answer.Keys) { $answers[$key] = $Answer[$key] } } if ($Interactive) { $questions = $Section ? @($catalog.Questions | Where-Object { $_.Section -in $Section }) : $catalog.Questions $answers = Read-RaidinessIntakeInteractive -Questions $questions -Answers $answers } # Validate before writing: a refused answer must not reach the engine, and # an unknown key must not silently disappear either. $clean = [ordered]@{} foreach ($key in ($answers.Keys | Sort-Object)) { $value = $answers[$key] if ($null -eq $value -or "$value".Trim() -eq '') { continue } $question = $byKey[$key] if (-not $question) { Write-Warning "Unknown intake key '$key' — stored, but no check reads it. A typo, or a newer questionnaire?" $clean[$key] = $value continue } $clean[$question.Key] = Assert-RaidinessIntakeAnswer -Question $question -Value $value } if (-not $PSCmdlet.ShouldProcess($stored, "store $($clean.Count) intake answer(s)")) { if ($PassThru) { return [pscustomobject] $clean } return } New-Item -ItemType Directory -Path $dataPath -Force | Out-Null $envelope = [ordered]@{ raidinessIntake = [ordered]@{ version = '1.0' generatedAt = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') } answers = $clean } ConvertTo-Json -InputObject $envelope -Depth 8 | Out-File -FilePath $stored -Encoding utf8 $total = $catalog.Questions.Count Write-Host "Intake stored: $($clean.Count) of $total question(s) answered — $stored" -ForegroundColor Green $personal = @($catalog.Questions | Where-Object { $_.Personal -and $clean.Contains($_.Key) }) if ($personal.Count -gt 0) { Write-Host " $($personal.Count) answer(s) name a person; those are hashed in the LLM bundle unless -IncludeIdentities." -ForegroundColor Cyan } Write-RaidinessLog -Phase 'intake' -Message "$($clean.Count) of $total answered" if ($PassThru) { [pscustomobject] $clean } } |