#!/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"