Private/Read-RaidinessIntakeCatalog.ps1

<#
    The questionnaire, assembled from the three seeds that already hold it.

    The structure of a question -- its type, its options, which section it
    belongs to -- lives in intake.questions.json. Its *text* lives where it
    already lived: in report.template.json for the intake questions and in
    guided-forms.json for the manual fields. Copying the text into a fourth
    file would be the fourth place it could go stale.
#>

function Read-RaidinessIntakeCatalog {
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param()

    $assets = Join-Path $PSScriptRoot '..' 'assets'
    foreach ($name in 'intake.questions.json', 'guided-forms.json', 'report.template.json') {
        if (-not (Test-Path (Join-Path $assets $name))) {
            throw "The questionnaire asset $name is missing. In a source checkout, build it first: bun run module:build"
        }
    }

    $questions = Get-Content -Path (Join-Path $assets 'intake.questions.json') -Raw | ConvertFrom-Json
    $forms = (Get-Content -Path (Join-Path $assets 'guided-forms.json') -Raw | ConvertFrom-Json).forms
    $template = Get-Content -Path (Join-Path $assets 'report.template.json') -Raw | ConvertFrom-Json
    $intakeKeys = $template.intakeKeys

    $sectionTitles = @{}
    foreach ($section in $questions.sections) { $sectionTitles[$section.key] = $section.title }

    # Field text, keyed by the measurement key the field writes.
    $fieldText = @{}
    foreach ($formName in $forms.PSObject.Properties.Name) {
        foreach ($field in $forms.$formName.fields) {
            $fieldText[$field.key] = [pscustomobject]@{
                Label = $field.label_nl
                Help  = $field.PSObject.Properties['help_nl'] ? $field.help_nl : $null
            }
        }
    }

    $rows = foreach ($question in $questions.questions) {
        $text = $null
        $help = $null

        if ($intakeKeys.PSObject.Properties[$question.key]) {
            $text = $intakeKeys.($question.key)
        }
        foreach ($alias in @($question.PSObject.Properties['aliasFor'] ? $question.aliasFor : @())) {
            if (-not $text -and $intakeKeys.PSObject.Properties[$alias]) { $text = $intakeKeys.$alias }
        }
        if ($fieldText.ContainsKey($question.key)) {
            if (-not $text) { $text = $fieldText[$question.key].Label }
            $help = $fieldText[$question.key].Help
        }
        if (-not $text) { $text = $question.key }

        [pscustomobject]@{
            PSTypeName = 'Raidiness.IntakeQuestion'
            Key        = $question.key
            Section    = $question.section
            SectionTitle = $sectionTitles.ContainsKey($question.section) ? $sectionTitles[$question.section] : $question.section
            Question   = $text
            Help       = $help
            Type       = $question.type
            Options    = @($question.PSObject.Properties['options'] ? $question.options : @())
            Optional   = [bool] ($question.PSObject.Properties['optional'] -and $question.optional)
            Personal   = [bool] ($question.PSObject.Properties['personal'] -and $question.personal)
            Aliases    = @($question.PSObject.Properties['aliasFor'] ? $question.aliasFor : @())
            Answer     = $null
        }
    }

    [pscustomobject]@{
        Sections  = $questions.sections
        Questions = @($rows)
    }
}