Private/Get-DebateQualityMetrics.ps1
|
# Copyright (c) 2026 Jeffrey Snover. All rights reserved. # Licensed under the MIT License. See LICENSE file in the project root. # Pure-compute quality metrics extraction from a saved debate JSON file. # Lifted from the inner Extract-QualityMetrics function in Compare-DebateQuality # so Measure-DebateQuality can reuse the same shape. # Dot-sourced by AITriad.psm1 — do NOT export. function Get-DebateQualityMetrics { <# .SYNOPSIS Extracts quality metrics from a saved debate JSON file. .DESCRIPTION Returns an ordered hashtable with five sections: header — topic, model, temperature, protocol, version, timestamp, round count calibration — crux/taxonomy/coherence ratios from calibration_log prm — process-reward mean/stddev/min prm_components — averaged per-component PRM scores agents — per-agent utility scores Pure compute against the debate JSON; no LLM call. Missing fields default to zero so the shape is stable across debate-engine versions. .PARAMETER Path Absolute path to the debate JSON. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Path ) Set-StrictMode -Version Latest function Get-SafeProp($Obj, [string]$Name, $Default = $null) { if ($null -eq $Obj) { return $Default } if ($Obj.PSObject.Properties[$Name]) { return $Obj.$Name } return $Default } $S = Get-Content $Path -Raw | ConvertFrom-Json $Header = [ordered]@{ topic = Get-SafeProp $S 'title' model = Get-SafeProp $S 'debate_model' temperature = Get-SafeProp $S 'debate_temperature' protocol = Get-SafeProp $S 'protocol_id' app_version = Get-SafeProp $S 'app_version' created_at = Get-SafeProp $S 'created_at' total_rounds = if ($S.PSObject.Properties['transcript'] -and $S.transcript) { @($S.transcript).Count } else { 0 } } $CL = Get-SafeProp $S 'calibration_log' $Calibration = [ordered]@{ crux_addressed_ratio = if ($CL) { [Math]::Round((Get-SafeProp $CL 'crux_addressed_ratio' 0), 4) } else { 0 } repetition_rate = if ($CL) { [Math]::Round((Get-SafeProp $CL 'repetition_rate' 0), 4) } else { 0 } claims_forgotten_rate = if ($CL) { [Math]::Round((Get-SafeProp $CL 'claims_forgotten_rate' 0), 4) } else { 0 } taxonomy_mapped_ratio = if ($CL) { [Math]::Round((Get-SafeProp $CL 'taxonomy_mapped_ratio' 0), 4) } else { 0 } avg_utilization_rate = if ($CL) { [Math]::Round((Get-SafeProp $CL 'avg_utilization_rate' 0), 4) } else { 0 } situation_crux_alignment = if ($CL) { [Math]::Round((Get-SafeProp $CL 'situation_crux_alignment' 0), 4) } else { 0 } avg_branch_cohesion = if ($CL) { [Math]::Round((Get-SafeProp $CL 'avg_branch_cohesion' 0), 4) } else { 0 } draft_repair_rate = if ($CL) { [Math]::Round((Get-SafeProp $CL 'draft_repair_rate' 0), 4) } else { 0 } concession_cascades = if ($CL) { [int](Get-SafeProp $CL 'concession_cascades' 0) } else { 0 } topic_alignment_rate = if ($CL) { [Math]::Round((Get-SafeProp $CL 'topic_alignment_rate' 0), 4) } else { 0 } } $PRM = [ordered]@{ mean = if ($CL) { [Math]::Round((Get-SafeProp $CL 'process_reward_mean' 0), 4) } else { 0 } stddev = if ($CL) { [Math]::Round((Get-SafeProp $CL 'process_reward_stddev' 0), 4) } else { 0 } min = if ($CL) { [Math]::Round((Get-SafeProp $CL 'process_reward_min' 0), 4) } else { 0 } } $PRMComponents = [ordered]@{} if ($S.PSObject.Properties['process_rewards'] -and $S.process_rewards) { $CompSums = @{}; $CompCounts = @{} foreach ($PR in @($S.process_rewards)) { if ($null -eq $PR) { continue } $Comps = Get-SafeProp $PR 'components' if ($null -eq $Comps) { continue } foreach ($Prop in $Comps.PSObject.Properties) { if (-not $CompSums.ContainsKey($Prop.Name)) { $CompSums[$Prop.Name] = 0.0 $CompCounts[$Prop.Name] = 0 } $CompSums[$Prop.Name] += [double]$Prop.Value $CompCounts[$Prop.Name]++ } } foreach ($Key in ($CompSums.Keys | Sort-Object)) { $PRMComponents[$Key] = [Math]::Round($CompSums[$Key] / $CompCounts[$Key], 4) } } $Agents = [ordered]@{} if ($CL -and $CL.PSObject.Properties['agent_utilities'] -and $CL.agent_utilities) { foreach ($Prop in $CL.agent_utilities.PSObject.Properties) { $AU = $Prop.Value $Agents[$Prop.Name] = [ordered]@{ composite = [Math]::Round((Get-SafeProp $AU 'composite' 0), 4) crux_engagement = [Math]::Round((Get-SafeProp $AU 'crux_engagement' 0), 4) topic_coherence = [Math]::Round((Get-SafeProp $AU 'topic_coherence' 0), 4) } } } if ($CL -and $CL.PSObject.Properties['topic_coherence_per_speaker'] -and $CL.topic_coherence_per_speaker) { foreach ($Prop in $CL.topic_coherence_per_speaker.PSObject.Properties) { if (-not $Agents.Contains($Prop.Name)) { $Agents[$Prop.Name] = [ordered]@{ composite = 0; crux_engagement = 0; topic_coherence = 0 } } $Agents[$Prop.Name]['topic_coherence'] = [Math]::Round([double]$Prop.Value, 4) } } return [ordered]@{ header = $Header calibration = $Calibration prm = $PRM prm_components = $PRMComponents agents = $Agents has_calibration_log = ($null -ne $CL) } } |