ai-conversation-impact/.claude/hooks/show-aggregate.sh
claude 60eca18c85 Add aggregate dashboard for portfolio-level social cost metrics
New show-aggregate.sh script computes cross-session metrics:
monoculture index, spend concentration by provider, automation
profile distribution, code quality signals, and data pollution
risk summary. Integrated into toolkit installer and README.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 15:09:28 +00:00

126 lines
4.6 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# show-aggregate.sh — Portfolio-level impact metrics across all sessions.
#
# Reads impact-log.jsonl and computes aggregate social cost proxies:
# monoculture index, spend concentration, automation profile, quality signals.
#
# Usage: ./show-aggregate.sh
set -euo pipefail
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"
LOG_FILE="$PROJECT_DIR/.claude/impact/impact-log.jsonl"
if [ ! -f "$LOG_FILE" ]; then
echo "No impact log found at $LOG_FILE"
exit 0
fi
python3 -c "
import json, sys
from collections import defaultdict
sessions = {} # session_id -> latest entry (deduplicate)
with open(sys.argv[1]) as f:
for line in f:
try:
d = json.loads(line.strip())
sid = d.get('session_id', '')
if sid:
sessions[sid] = d
except Exception:
pass
if not sessions:
print('No sessions found.')
sys.exit(0)
entries = list(sessions.values())
n = len(entries)
# --- Environmental totals ---
total_energy = sum(e.get('energy_wh', 0) for e in entries)
total_co2 = sum(e.get('co2_g', 0) for e in entries)
total_cost_cents = sum(e.get('cost_cents', 0) for e in entries)
total_output = sum(e.get('output_tokens', 0) for e in entries)
total_input = sum(e.get('cumulative_input_tokens', 0) for e in entries)
print(f'=== Aggregate Impact ({n} sessions) ===')
print()
print('--- Environmental & Financial ---')
print(f' Total energy: ~{total_energy} Wh')
print(f' Total CO2: ~{total_co2}g')
print(f' Total cost: ~\${total_cost_cents / 100:.2f}')
print(f' Total tokens: ~{total_input:,} input, ~{total_output:,} output')
print()
# --- Monoculture index ---
model_counts = defaultdict(int)
model_spend = defaultdict(int)
for e in entries:
mid = e.get('model_id', '') or 'unknown'
model_counts[mid] += 1
model_spend[mid] += e.get('cost_cents', 0)
print('--- Provider Concentration ---')
for model, count in sorted(model_counts.items(), key=lambda x: -x[1]):
pct = count * 100 / n
spend = model_spend[model] / 100
print(f' {model}: {count}/{n} sessions ({pct:.0f}%), \${spend:.2f} spend')
if len(model_counts) == 1:
print(' Monoculture index: 1.0 (single provider — maximum concentration)')
else:
dominant = max(model_counts.values())
print(f' Monoculture index: {dominant / n:.2f} (dominant provider share)')
print()
# --- Automation profile ---
auto_ratios = [e.get('automation_ratio_pm', 0) for e in entries if e.get('automation_ratio_pm') is not None]
if auto_ratios:
avg_auto = sum(auto_ratios) / len(auto_ratios) / 10 # convert permille to %
min_auto = min(auto_ratios) / 10
max_auto = max(auto_ratios) / 10
# Categorize sessions
low = sum(1 for r in auto_ratios if r < 500) # <50%
med = sum(1 for r in auto_ratios if 500 <= r < 800) # 50-80%
high = sum(1 for r in auto_ratios if r >= 800) # >80%
print('--- Automation Profile (Deskilling Risk) ---')
print(f' Average automation ratio: {avg_auto:.1f}%')
print(f' Range: {min_auto:.1f}% - {max_auto:.1f}%')
print(f' Low risk (<50%): {low} sessions')
print(f' Medium (50-80%): {med} sessions')
print(f' High risk (>80%): {high} sessions')
print()
# --- Code quality signals ---
total_test_pass = sum(e.get('test_passes', 0) for e in entries)
total_test_fail = sum(e.get('test_failures', 0) for e in entries)
total_edits = sum(e.get('total_file_edits', 0) for e in entries)
total_files = sum(e.get('unique_files_edited', 0) for e in entries)
sessions_with_tests = sum(1 for e in entries if e.get('test_passes', 0) + e.get('test_failures', 0) > 0)
print('--- Code Quality Signals ---')
print(f' Total file edits: {total_edits} across {total_files} unique files')
if total_files > 0:
print(f' Average churn: {total_edits / total_files:.1f} edits/file')
if sessions_with_tests > 0:
print(f' Test results: {total_test_pass} passed, {total_test_fail} failed ({sessions_with_tests} sessions with tests)')
if total_test_pass + total_test_fail > 0:
fail_rate = total_test_fail * 100 / (total_test_pass + total_test_fail)
print(f' Test failure rate: {fail_rate:.0f}%')
else:
print(f' Tests: none detected in any session')
print()
# --- Data pollution risk ---
push_sessions = sum(1 for e in entries if e.get('has_public_push', 0))
print('--- Data Pollution Risk ---')
print(f' Sessions with public push: {push_sessions}/{n} ({push_sessions * 100 / n:.0f}%)')
print(f' Tokens pushed publicly: ~{sum(e.get(\"output_tokens\", 0) for e in entries if e.get(\"has_public_push\", 0)):,}')
print()
" "$LOG_FILE"