Public/Test-SynthesisCompleteness.ps1

# Copyright (c) 2026 Jeffrey Snover. All rights reserved.
# Licensed under the MIT License. See LICENSE file in the project root.

function Test-SynthesisCompleteness {
    <#
    .SYNOPSIS
        Validates synthesis completeness in a debate session JSON.
    .DESCRIPTION
        Finds the concluding transcript entry, checks metadata.synthesis for
        8 expected keys, and optionally cross-references diagnostics raw_response
        to detect parse-loss (data generated but lost in parsing).
    .PARAMETER Session
        Path to a debate session JSON file.
    .PARAMETER PassThru
        Return the results object for piping.
    .EXAMPLE
        Test-SynthesisCompleteness -Session debate-72a6e0a2.json
    .EXAMPLE
        Test-SynthesisCompleteness -Session debate-72a6e0a2.json -PassThru
    .LINK
        Show-AITriadHelp
    .LINK
        Compare-DebateQuality
    .LINK
        Compare-DebateRuns
    .LINK
        Measure-DebateQuality
    .LINK
        Invoke-DebateAB
    .LINK
        Get-CalibrationTrend
    .LINK
        Test-AITJudgeModel
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path $_ })]
        [string]$Session,

        [switch]$PassThru
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'

    $ExpectedKeys = @(
        'areas_of_agreement'
        'areas_of_disagreement'
        'cruxes'
        'unresolved_questions'
        'taxonomy_coverage'
        'argument_map'
        'preferences'
        'policy_implications'
    )

    $Data = Get-Content -Raw -Path $Session | ConvertFrom-Json

    # Find the concluding entry
    $Concluding = $null
    if ($Data.PSObject.Properties['transcript'] -and $Data.transcript) {
        $Concluding = @($Data.transcript | Where-Object {
            $_.PSObject.Properties['type'] -and $_.type -eq 'concluding'
        }) | Select-Object -First 1
    }

    if ($null -eq $Concluding) {
        Write-Host "`n No concluding entry found in transcript." -ForegroundColor Yellow
        Write-Host " Debate may not have reached synthesis phase." -ForegroundColor DarkGray
        if ($PassThru) {
            return [PSCustomObject]@{
                Session       = $Session
                HasConcluding = $false
                Score         = 0
                Total         = $ExpectedKeys.Count
                Fields        = @()
            }
        }
        return
    }

    # Extract synthesis object
    $Synthesis = $null
    if ($Concluding.PSObject.Properties['metadata'] -and $null -ne $Concluding.metadata -and
        $Concluding.metadata.PSObject.Properties['synthesis'] -and $null -ne $Concluding.metadata.synthesis) {
        $Synthesis = $Concluding.metadata.synthesis
    }

    # Load diagnostics for raw_response cross-reference
    $DiagEntries = @()
    $ConcludingId = if ($Concluding.PSObject.Properties['id']) { $Concluding.id } else { $null }
    if ($Data.PSObject.Properties['diagnostics'] -and $Data.diagnostics.PSObject.Properties['entries']) {
        $DiagEntries = @($Data.diagnostics.entries)
    }

    # Find raw responses for the concluding entry
    $RawResponses = @{}
    if ($ConcludingId -and $DiagEntries.Count -gt 0) {
        foreach ($Diag in $DiagEntries) {
            if ($null -eq $Diag) { continue }
            if (-not $Diag.PSObject.Properties['entry_id']) { continue }
            if ($Diag.entry_id -ne $ConcludingId) { continue }
            if ($Diag.PSObject.Properties['raw_response'] -and $Diag.raw_response) {
                $RawText = $Diag.raw_response
                foreach ($K in $ExpectedKeys) {
                    if ($RawText -match [regex]::Escape($K)) {
                        $RawResponses[$K] = $true
                    }
                }
            }
        }
    }

    # Evaluate each field
    $Results = [System.Collections.Generic.List[PSObject]]::new()
    $FilledCount = 0

    foreach ($Key in $ExpectedKeys) {
        $Present = $false
        $Count = 0
        $Status = 'MISSING'

        if ($null -ne $Synthesis -and $Synthesis.PSObject.Properties[$Key] -and $null -ne $Synthesis.$Key) {
            $Items = @($Synthesis.$Key)
            $Count = $Items.Count
            if ($Count -gt 0) {
                $Present = $true
                $Status = "OK ($Count entries)"
                $FilledCount++
            }
        }

        if (-not $Present) {
            if ($RawResponses.ContainsKey($Key)) {
                $Status = 'EMPTY (raw response had data — PARSE LOSS)'
            } elseif ($null -eq $Synthesis) {
                $Status = 'MISSING (no synthesis object)'
            } else {
                $Status = 'EMPTY'
            }
        }

        $Results.Add([PSCustomObject]@{
            Key    = $Key
            Status = $Status
            Count  = $Count
        })
    }

    # Determine overall rating
    $Rating = if ($FilledCount -eq $ExpectedKeys.Count) { 'COMPLETE' }
              elseif ($FilledCount -ge 6) { 'GOOD' }
              elseif ($FilledCount -ge 4) { 'PARTIAL' }
              elseif ($FilledCount -ge 1) { 'DEGRADED' }
              else { 'EMPTY' }

    $RatingColor = switch ($Rating) {
        'COMPLETE' { 'Green' }
        'GOOD'     { 'Green' }
        'PARTIAL'  { 'Yellow' }
        'DEGRADED' { 'Red' }
        'EMPTY'    { 'Red' }
    }

    # Display
    Write-Host "`n SYNTHESIS COMPLETENESS" -ForegroundColor White
    Write-Host " $('─' * 55)" -ForegroundColor DarkGray

    foreach ($R in $Results) {
        $FieldColor = if ($R.Count -gt 0) { 'Green' }
                      elseif ($R.Status -like '*PARSE LOSS*') { 'Red' }
                      else { 'Yellow' }
        $Label = ($R.Key -replace '_', ' ')
        Write-Host (' {0,-30} : ' -f $Label) -ForegroundColor Gray -NoNewline
        Write-Host $R.Status -ForegroundColor $FieldColor
    }

    Write-Host " $('─' * 55)" -ForegroundColor DarkGray
    Write-Host " Completeness: $FilledCount/$($ExpectedKeys.Count) — " -ForegroundColor White -NoNewline
    Write-Host $Rating -ForegroundColor $RatingColor
    Write-Host ''

    if ($PassThru) {
        return [PSCustomObject]@{
            Session       = $Session
            HasConcluding = $true
            Score         = $FilledCount
            Total         = $ExpectedKeys.Count
            Rating        = $Rating
            Fields        = $Results.ToArray()
        }
    }
}