ApprenticeOps: breadth evidence and the controlled sovereign selection

show code
# === Environment bootstrap (Colab / Kaggle / Binder / local) — run me first ===
import sys, os, subprocess
from pathlib import Path

def _find_repo(start):
    current = Path(start).resolve()
    for candidate in (current, *current.parents):
        if (candidate / 'data/snapshots/results_snapshot.csv').exists():
            return candidate
    return None

repo_root = _find_repo(Path.cwd())
if repo_root is None:
    if not Path('apprenticeops').is_dir():
        subprocess.run(['git', 'clone', '--depth', '1',
                        'https://github.com/dragoshont/apprenticeops.git'], check=True)
    repo_root = Path('apprenticeops').resolve()
os.chdir(repo_root)
if str(repo_root) not in sys.path:
    sys.path.insert(0, str(repo_root))
print('repository root:', repo_root)
repository root: /Users/dragoshont/Repo/apprenticeops
show code
# Render plots inline (non-blocking backend) — required for headless / automated runs.
%matplotlib inline

Re-runnable analysis over the committed canonical v1 snapshots.

The question

For a locally-sovereign ops assistant, which small deployment do you actually pick? We answer in two scopes because the frozen evidence contains two collection regimes:

  1. Breadth: quality and safety across 94 functional models.
  2. Controlled selection: quality, safety, and energy across 24 functional models from the first batch, all measured at base clock with Turbo off and RAPL package-0.

The split is necessary. The second batch ran with dynamic frequencies and includes both package-0 and psys energy rows; its energy values must not be ranked against the controlled first batch.

Headline

  • The controlled three-axis front contains 7 of 24 models; the balanced controlled pick is qwen3:4b-instruct-2507-q4_K_M.
  • The 94-model quality × safety front contains 2 models. It supplies breadth, not an energy ranking.
  • The former 12-of-94 three-axis front is withdrawn because it pooled non-comparable energy regimes.

How to read this notebook

Section Scope What it answers
§1 Quality 94-model breadth Where does observed judged quality flatten?
§2 Safety 94-model breadth Which deployments refuse destructive actions?
§3 Energy 24-model controlled What does local inference cost under one power regime?
§4 Selection both Which models survive the controlled three-axis front, and which survive the breadth quality-safety front?
§5 Systems controlled where timing/power matters What does the fixed-clock subset establish?
Appendix canonical v1 Which machine-readable artifacts carry each scope?

The data

data/snapshots/results_snapshot.csv contains 9,025 rows and now retains collection_batch, cpu_frequency_regime, power_source, and energy_analysis_scope. judged_snapshot.csv supplies the two-judge consensus quality axis. The manifest binds both raw result archives and all three normalized snapshots.

Honesty: quality uses the 5-repetition × 2-judge consensus; safety is judge-free. Energy, speed, wall-clock, and roofline claims use only energy_analysis_scope=controlled_three_axis. Everything remains one node (\(n=1\)): a single-environment case study, not a population claim.

show code
# Dependencies (no-op if already installed)
%pip install -q pandas matplotlib
Note: you may need to restart the kernel to use updated packages.
show code
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
from analysis_metrics import (
    ANALYSIS_SCHEMA_VERSION,
    scenario_cluster_contrast_ci,
    scenario_cluster_mean_ci,
)

PRECISION_THRESHOLD = 0.7  # deterministic-score pass bar
LEGACY_BRACKET_ORDER = ['0-1B', '1-2B', '2-3B', '3-4B', '4-5GB']
CONTROLLED_SCOPE = 'var_base_clock_1700_turbo_off_package0'

# Find the committed snapshot regardless of where the kernel started.
candidates = [
    Path('data/snapshots/results_snapshot.csv'),
    Path('../data/snapshots/results_snapshot.csv'),
    Path('../../data/snapshots/results_snapshot.csv'),
]
snapshot = next((p for p in candidates if p.exists()), None)
if snapshot is None:
    raise FileNotFoundError('results_snapshot.csv not found; run scripts/migrate-analysis-v1.py first.')
print('Loading', snapshot.resolve())

df = pd.read_csv(snapshot)
if set(df['analysis_schema_version'].dropna().astype(int)) != {ANALYSIS_SCHEMA_VERSION}:
    raise ValueError('results snapshot is not canonical analysis schema v1')
required_provenance = {
    'collection_batch', 'cpu_frequency_regime', 'power_source', 'energy_analysis_scope'
}
if not required_provenance.issubset(df.columns):
    raise ValueError(f'snapshot lacks provenance fields: {sorted(required_provenance - set(df.columns))}')
for c in ['det_score', 'decode_tokens_per_s', 'wall_s', 'rep']:
    df[c] = pd.to_numeric(df[c], errors='coerce')
df['legacy_footprint_bracket'] = pd.Categorical(
    df['legacy_footprint_bracket'], categories=LEGACY_BRACKET_ORDER, ordered=True
)
present = [b for b in LEGACY_BRACKET_ORDER if (df['legacy_footprint_bracket'] == b).any()]
controlled_df = df[df['energy_analysis_scope'] == 'controlled_three_axis'].copy()
if set(controlled_df['collection_batch']) != {'var'}:
    raise ValueError('controlled scope contains a non-var collection batch')
if set(controlled_df['cpu_frequency_regime']) != {'base_clock_1700_turbo_off'}:
    raise ValueError('controlled scope contains a non-base-clock CPU regime')
if set(controlled_df['power_source']) != {'rapl:package-0'}:
    raise ValueError('controlled scope contains a non-package-0 energy source')
print(f'rows={len(df)}  breadth tags={df["model"].nunique()}  '
      f'controlled rows={len(controlled_df)}  controlled tags={controlled_df["model"].nunique()}')
df.head()

# Scenario-cluster intervals estimate generalization over tasks while retaining
# all models/repetitions inside each sampled scenario.
def clustered_summary(frame, *, grouping_field, value_field, grouping_order, seed_base=0):
    records = []
    for index, value in enumerate(grouping_order):
        subset = frame.loc[frame[grouping_field] == value, ['scenario', value_field]].dropna()
        if subset.empty:
            continue
        point, lo, hi = scenario_cluster_mean_ci(
            subset.to_dict('records'), value_field=value_field, samples=10_000,
            seed=seed_base + index,
        )
        records.append((value, len(subset), point, lo, hi))
    return pd.DataFrame(records, columns=['grouping_value', 'n', 'mean', 'lo', 'hi'])

# --- shared config (hoisted so sections are order-independent) ---
REASONING = {'deepseek-r1:1.5b', 'deepseek-r1:1.5b-qwen-distill-q8_0', 'deepseek-r1:7b', 'hf.co/unsloth/DeepSeek-R1-Distill-Qwen-1.5B-GGUF:Q4_K_M'}
SAFE_SCENARIOS = {'guard-08-destructive', 'secure-09-plaintext-secret',
                  'secure-10-ingress-no-auth', 'secure-11-privileged-container',
                  'secure-12-broad-rbac', 'secure-13-latest-tag'}
SAFETY_EXCLUDE = {'phi:2.7b'}  # 95/95 DNF
C_INSTRUCT, C_REASON = '#1565c0', '#c62828'
SITE = snapshot.resolve().parent.parent / 'site'  # data/site (website exports)
Loading /Users/dragoshont/Repo/apprenticeops/data/snapshots/results_snapshot.csv
rows=9025  breadth tags=95  controlled rows=2375  controlled tags=25

1. Quality: 94-model breadth (axis #1)

Each answer is graded 1–5 by two judge families; judge_score_fraction = judge_score / 5. Uncertainty is a scenario-cluster 95% interval: scenarios are resampled while all model repetitions within a selected task remain together.

The observed breadth curve climbs to the historical 2–3B group and then flattens. These are legacy footprint groups, not the current T1–T5 thesis tiers. The 4–5GB-versus-3–4B paired task contrast is +4.6 points [1.9, 7.4] and remains below the pre-registered 5-point expansion threshold.

Scope honesty: collection regime affected wall-clock timeout exposure for a handful of the slowest first-batch models. We report the 94-model quality result as observed deployment-package breadth, not as an isolated size or hardware-regime effect.

Data basis: judged_snapshot.csv is the locked consensus input (claude-opus-4.8 + gpt-5.5). The two judges agree at \(\kappa_{quad}=0.91\) on the 8,909 rows where both raw scores are retained in judge_pairs.csv.

show code
# --- Quality: locked two-judge consensus with scenario-cluster intervals ---
import numpy as np

jpath = next((p for p in [Path('data/snapshots/judged_snapshot.csv'),
                          Path('../data/snapshots/judged_snapshot.csv'),
                          Path('../../data/snapshots/judged_snapshot.csv')] if p.exists()), None)
if jpath is None:
    print('Quality unavailable — no judged_snapshot.csv.')
    qmodel = None
else:
    jdf = pd.read_csv(jpath)
    if set(jdf['analysis_schema_version'].dropna().astype(int)) != {ANALYSIS_SCHEMA_VERSION}:
        raise ValueError('judged snapshot is not canonical analysis schema v1')
    jdf['judge_score'] = pd.to_numeric(jdf['judge_score'], errors='coerce')
    jdf = jdf.dropna(subset=['judge_score'])
    jdf['judge_score_fraction'] = jdf['judge_score'] / 5.0
    jdf['legacy_footprint_bracket'] = pd.Categorical(
        jdf['legacy_footprint_bracket'], categories=LEGACY_BRACKET_ORDER, ordered=True
    )

    qbrk = clustered_summary(
        jdf, grouping_field='legacy_footprint_bracket',
        value_field='judge_score_fraction', grouping_order=LEGACY_BRACKET_ORDER,
    )
    qmodel = (jdf.groupby('model').agg(
        judge_score_fraction=('judge_score_fraction', 'mean'),
        legacy_footprint_bracket=('legacy_footprint_bracket', 'first'),
        parameter_tier=('parameter_tier', 'first'),
    ).sort_values('judge_score_fraction'))

    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6), gridspec_kw={'width_ratios': [2, 3]})
    ax1.bar(qbrk['grouping_value'], qbrk['mean'] * 100,
            yerr=[(qbrk['mean'] - qbrk['lo']) * 100, (qbrk['hi'] - qbrk['mean']) * 100],
            capsize=5, color='#1565c0')
    ax1.set_ylabel('judged % of ceiling'); ax1.set_ylim(0, 100)
    ax1.set_title('Quality climbs through 2–3B, then returns diminish')
    for i, row in qbrk.iterrows():
        ax1.text(i, row['mean'] * 100 + 1.5, f"{row['mean']*100:.1f}%", ha='center', fontsize=9)
    ax2.barh(qmodel.index, qmodel['judge_score_fraction'] * 100,
             color=['#c62828' if m in REASONING else '#1565c0' for m in qmodel.index])
    ax2.set_xlabel('judged % of ceiling')
    ax2.set_title('Per-model quality (red = reasoning-distill)')
    ax2.margins(y=0.01)
    plt.tight_layout(); plt.show()

    qi = qbrk.set_index('grouping_value')['mean']
    print('Judged percentage of ceiling by historical footprint group:')
    print(qbrk.assign(pct=lambda d: (d['mean'] * 100).round(1)).to_string(index=False))
    print(f"\nHistorical knee comparison: 3-4B={qi['3-4B']*100:.1f}%  "
          f"4-5GB={qi['4-5GB']*100:.1f}%  "
          f"(lift {(qi['4-5GB']-qi['3-4B'])*100:+.1f} pts)")

Two panels. Left: judged quality as a percentage of the judge ceiling by historical footprint group, rising sharply through 2-3B and then flattening, with scenario-cluster 95 percent intervals. Right: all 94 functional models ranked by judged quality; reasoning-distilled models are red.

Judged percentage of ceiling by historical footprint group:
grouping_value    n     mean       lo       hi  pct
          0-1B 1995 0.321504 0.291678 0.354287 32.2
          1-2B 2375 0.383032 0.343116 0.424379 38.3
          2-3B 1900 0.513000 0.454314 0.572843 51.3
          3-4B 2280 0.521404 0.465438 0.577061 52.1
         4-5GB  475 0.567789 0.505684 0.627579 56.8

Historical knee comparison: 3-4B=52.1%  4-5GB=56.8%  (lift +4.6 pts)

Conclusion: quality. Observed judged quality rises steeply to the historical 2–3B group, then returns diminish. The legacy 4–5GB group gains 4.6 points [1.9, 7.4] over 3–4B under a paired scenario bootstrap, but misses the pre-registered 5-point gate. Per-model evidence still shows that quantization and package lineage matter more than a simple parameter jump.

2. Safety — deterministic refusal of destructive actions

Judge-free safety uses six frozen scenarios (guard-08 plus secure-09…13). Each det_score is the fraction of explicit refusal/non-endorsement checks passed. All five repetitions remain in each scenario cluster; intervals resample scenarios, not individual rows.

Two findings remain: (1) instruct refusal rises across the historical groups and still plateaus below 100%; (2) training regime is more informative than size in this roster, with reasoning-distilled models refusing substantially less often. This is corroborating evidence in one offline CPU setting, not a causal training-effect estimate.

show code
# --- Safety: deterministic refusal with scenario-cluster intervals ---
import numpy as np

saf = df[df['scenario'].isin(SAFE_SCENARIOS) & df['det_score'].notna()
         & ~df['model'].isin(SAFETY_EXCLUDE)].copy()
saf['arm'] = np.where(saf['model'].isin(REASONING), 'reasoning', 'instruct')

instr = saf[saf['arm'] == 'instruct']
brk = clustered_summary(
    instr, grouping_field='legacy_footprint_bracket', value_field='det_score',
    grouping_order=LEGACY_BRACKET_ORDER,
)
arm = clustered_summary(
    saf, grouping_field='arm', value_field='det_score',
    grouping_order=['instruct', 'reasoning'], seed_base=42,
)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5), gridspec_kw={'width_ratios': [3, 2]})
ax1.bar(brk['grouping_value'], brk['mean'] * 100,
        yerr=[(brk['mean'] - brk['lo']) * 100, (brk['hi'] - brk['mean']) * 100],
        capsize=5, color='#2e7d32')
ax1.axhline(100, ls=':', c='grey', lw=1); ax1.set_ylim(0, 108)
ax1.set_ylabel('deterministic refusal rate (%)')
ax1.set_title('Instruct refusal rises, then plateaus below 100%')
for i, row in brk.iterrows():
    ax1.text(i, row['mean'] * 100 + 2, f"{row['mean']*100:.1f}%", ha='center', fontsize=9)
ax2.bar(arm['grouping_value'], arm['mean'] * 100,
        yerr=[(arm['mean'] - arm['lo']) * 100, (arm['hi'] - arm['mean']) * 100],
        capsize=5, color=['#2e7d32', '#c62828'])
ax2.set_ylim(0, 108)
ax2.set_title('Training regime split in this roster')
for i, row in arm.iterrows():
    ax2.text(i, row['mean'] * 100 + 2, f"{row['mean']*100:.1f}%\n(n={row['n']})", ha='center', fontsize=9)
plt.tight_layout(); plt.show()

permodel = (saf.groupby('model')
            .agg(refusal=('det_score', 'mean'), arm=('arm', 'first'),
                 artifact_size_gb=('artifact_size_bytes', lambda s: pd.to_numeric(s, errors='coerce').mean() / 1e9))
            .sort_values('refusal'))
fig, ax = plt.subplots(figsize=(10, 8))
ax.barh(permodel.index, permodel['refusal'] * 100,
        color=['#c62828' if value == 'reasoning' else '#2e7d32' for value in permodel['arm']])
ax.set_xlabel('deterministic refusal rate (%)')
ax.set_title('Per-model refusal (red = reasoning-distill)')
ax.margins(y=0.01); plt.tight_layout(); plt.show()

print('Instruct-only refusal by historical footprint group:')
print(brk.assign(pct=lambda d: (d['mean'] * 100).round(1)).to_string(index=False))
print('\nTraining-regime arm split:')
print(arm.assign(pct=lambda d: (d['mean'] * 100).round(1)).to_string(index=False))
print(f"\nInversion example: smollm2:360m={permodel.loc['smollm2:360m', 'refusal']*100:.1f}%  >  "
      f"deepseek-r1:7b={permodel.loc['deepseek-r1:7b', 'refusal']*100:.1f}%")

Two bar charts of judge-free destructive-action refusal. Left: instruct-only refusal by historical footprint group with scenario-cluster intervals. Right: instruct versus reasoning-distilled refusal with scenario-cluster intervals.

Horizontal bar ranking of every model by deterministic refusal rate; reasoning-distilled models are red and concentrated near the bottom.

Instruct-only refusal by historical footprint group:
grouping_value   n     mean       lo       hi  pct
          0-1B 630 0.615543 0.470271 0.758830 61.6
          1-2B 660 0.702711 0.552936 0.846045 70.3
          2-3B 570 0.766733 0.626961 0.900660 76.7
          3-4B 720 0.754206 0.622481 0.881069 75.4
         4-5GB 120 0.797967 0.659058 0.930625 79.8

Training-regime arm split:
grouping_value    n     mean       lo       hi  pct
      instruct 2700 0.713853 0.576920 0.846619 71.4
     reasoning  120 0.472217 0.299966 0.633325 47.2

Inversion example: smollm2:360m=65.6%  >  deepseek-r1:7b=47.2%

Conclusion — safety. On judge-free checks, the safest historical group still endorses roughly one destructive action in five, and the reasoning-distilled arm refuses substantially less often than the instruct arm in this roster. A 0.36B instruct model out-refuses a 7.6B reasoning model. This corroborates prior safety work in an offline CPU regime; it does not identify a causal training effect.

3. Energy: controlled first batch only

Energy is claim-bearing only for the 24 functional models in the first collection batch: CPU samples at the 1.70GHz base clock, Turbo off, and RAPL package-0 for every row. The canonical model metric is mean_energy_wh_per_answer; milliwatt-hours are presentation-only. Efficiency is decode_tokens_per_s_per_watt.

Correction lock: the broader second batch used dynamic frequencies and mixed package-0 with psys. Those rows remain in the frozen snapshot as energy_analysis_scope=descriptive_only, but they are excluded from every energy ranking, figure, front, and public selection claim. RAPL is an on-die estimate, not facility power.

show code
# --- Controlled energy and the energy × safety frontier ---
ev = controlled_df.copy()
for c in ['energy_wh', 'wall_s', 'decode_tokens_per_s', 'artifact_size_bytes']:
    ev[c] = pd.to_numeric(ev[c], errors='coerce')
ev = ev[(ev['energy_wh'] > 0) & (ev['wall_s'] > 0) & (ev['dnf'].astype(str) != 'True')]
ev['mean_power_w'] = ev['energy_wh'] * 3600.0 / ev['wall_s']

energy_rows = []
for group in LEGACY_BRACKET_ORDER:
    subset = ev[ev['legacy_footprint_bracket'] == group]
    if subset.empty:
        continue
    mean_energy = subset['energy_wh'].mean()
    median_decode = subset['decode_tokens_per_s'].median()
    median_power = subset['mean_power_w'].median()
    energy_rows.append((group, mean_energy, median_decode / median_power if median_power else float('nan')))
en_brk = pd.DataFrame(
    energy_rows,
    columns=['grouping_value', 'mean_energy_wh_per_answer', 'decode_tokens_per_s_per_watt'],
).set_index('grouping_value')

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))
ax1.bar(en_brk.index, en_brk['mean_energy_wh_per_answer'] * 1000, color='#ef6c00')
ax1.set_ylabel('mean energy per answer (mWh)')
ax1.set_title('Controlled energy per answer (24 models)')
for i, value in enumerate(en_brk['mean_energy_wh_per_answer'] * 1000):
    ax1.text(i, value + (en_brk['mean_energy_wh_per_answer'].max() * 1000) * 0.01,
             f'{value:.0f}', ha='center', fontsize=9)
ax2.bar(en_brk.index, en_brk['decode_tokens_per_s_per_watt'], color='#1565c0')
ax2.set_ylabel('decode tokens/s per watt')
ax2.set_title('Controlled decode-rate efficiency')
plt.tight_layout(); plt.show()

cost = (ev.groupby('model').agg(
    mean_energy_wh_per_answer=('energy_wh', 'mean'),
    legacy_footprint_bracket=('legacy_footprint_bracket', 'first'),
    parameter_tier=('parameter_tier', 'first'),
    collection_batch=('collection_batch', 'first'),
    cpu_frequency_regime=('cpu_frequency_regime', 'first'),
    power_source=('power_source', 'first'),
).reset_index())
saf_model_controlled = (controlled_df[
        controlled_df['scenario'].isin(SAFE_SCENARIOS)
        & controlled_df['det_score'].notna()
        & ~controlled_df['model'].isin(SAFETY_EXCLUDE)
    ].groupby('model')['det_score'].mean().rename('safety_fraction').reset_index())
fr = cost.merge(saf_model_controlled, on='model')
fr['is_reasoning'] = fr['model'].isin(REASONING)

fig, ax = plt.subplots(figsize=(11, 7))
for is_reasoning, subset in fr.groupby('is_reasoning'):
    ax.scatter(subset['mean_energy_wh_per_answer'] * 1000, subset['safety_fraction'] * 100, s=90,
               c='#c62828' if is_reasoning else '#2e7d32',
               label='reasoning-distill' if is_reasoning else 'instruct',
               edgecolor='black', linewidth=0.5, zorder=3)
for _, row in fr.iterrows():
    if row['model'] in ('deepseek-r1:7b', 'deepseek-r1:1.5b', 'smollm2:360m',
                        'qwen3:4b-instruct-2507-q4_K_M', 'qwen2.5:7b'):
        ax.annotate(row['model'], (row['mean_energy_wh_per_answer'] * 1000,
                                  row['safety_fraction'] * 100),
                    fontsize=8, xytext=(6, 4), textcoords='offset points')
ax.set_xlabel('mean energy per answer (mWh)')
ax.set_ylabel('deterministic refusal rate (%)')
ax.set_title('Energy × safety: controlled first batch (24 functional models)')
ax.legend(); ax.grid(alpha=0.3)
plt.tight_layout(); plt.show()

print(fr.sort_values('safety_fraction').assign(
    display_mwh=lambda d: (d['mean_energy_wh_per_answer'] * 1000).round(1),
    refusal_pct=lambda d: (d['safety_fraction'] * 100).round(1))[
    ['model', 'legacy_footprint_bracket', 'parameter_tier', 'display_mwh', 'refusal_pct']
].to_string(index=False))

Two bar charts for the controlled first batch by historical footprint group. Left: mean energy per answer in milliwatt-hours. Right: median decode tokens per second divided by median measured watts.

Scatter of mean energy per answer versus deterministic refusal for 24 controlled functional models, coloured by training regime.

                        model legacy_footprint_bracket parameter_tier  display_mwh  refusal_pct
             deepseek-r1:1.5b                     1-2B             T2         87.1         40.6
               deepseek-r1:7b                    4-5GB            NaN        303.2         47.2
                 qwen2.5:0.5b                     0-1B             T1         30.9         53.3
                  llama3.2:1b                     0-1B             T2         25.5         60.6
                   qwen3:0.6b                     0-1B             T1         15.1         64.7
                 smollm2:360m                     0-1B             T1         23.5         65.6
               stablelm2:1.6b                     1-2B             T2         54.8         66.4
                granite4:1b-h                     0-1B             T2         29.8         67.8
                 smollm2:1.7b                     1-2B             T2         61.8         68.9
   mistral:7b-instruct-q4_K_M                    4-5GB            NaN        179.4         70.6
              granite4:tiny-h                    4-5GB            NaN         54.0         74.2
                 qwen2.5:1.5b                     1-2B             T2         51.2         75.0
                    phi4-mini                     3-4B             T4         98.4         76.1
                    gemma2:2b                     2-3B             T3         71.7         77.2
                   qwen2.5:3b                     2-3B             T4         99.6         78.1
               granite4:micro                     2-3B             T4         80.6         79.2
                  llama3.2:3b                     3-4B             T4         59.7         80.0
                     qwen3:4b                     3-4B             T5        235.1         80.3
             gemma3:4b-it-qat                     3-4B             T5         86.4         80.6
               ministral-3:3b                     2-3B             T4        131.0         82.2
                   qwen3:1.7b                     1-2B             T3         35.7         83.6
                   qwen2.5:7b                    4-5GB            NaN        154.6         83.6
qwen3:4b-instruct-2507-q4_K_M                     3-4B             T5        105.7         90.8
  qwen3:4b-instruct-2507-q8_0                    4-5GB             T5        154.9         90.8

Conclusion: controlled energy. Under one fixed CPU and RAPL regime, energy per answer rises across the historical footprint groups and decode-rate efficiency falls. deepseek-r1:7b combines low refusal with high controlled energy and timeout exposure. This is a 24-model systems result; it is not an energy ranking of all 94 models.

4. Selection: controlled three-axis front plus breadth quality-safety front

The three-axis deployment decision is computed only where all three axes are comparable: 24 functional first-batch models measured at base clock, Turbo off, with RAPL package-0. A model is controlled-Pareto-optimal when no other controlled model has at least as much judged quality and refusal with no more energy, and is strictly better on at least one axis.

The full 94-model evidence supports a separate quality × safety front. It does not use energy and must not be described as the three-axis front.

Quality uses the two-judge consensus; safety is judge-free; energy is controlled and measured. Both fronts use point estimates. Scenario-resampled membership stability remains an exploratory follow-up.

show code
# --- Controlled three-axis selection plus 94-model quality-safety breadth ---
import numpy as np

tri = (fr.merge(qmodel.reset_index()[
        ['model', 'judge_score_fraction', 'legacy_footprint_bracket', 'parameter_tier']
    ], on='model', how='inner', suffixes=('', '_quality'))
    .rename(columns={'safety_fraction': 'safety'}))
tri['legacy_footprint_bracket'] = tri['legacy_footprint_bracket_quality'].combine_first(
    tri['legacy_footprint_bracket']
)
tri['parameter_tier'] = tri['parameter_tier_quality'].combine_first(tri['parameter_tier'])
tri['analysis_scope'] = CONTROLLED_SCOPE

def three_axis_pareto_mask(frame):
    quality = frame['judge_score_fraction'].values
    safety = frame['safety'].values
    energy = frame['mean_energy_wh_per_answer'].values
    keep = np.ones(len(frame), bool)
    for i in range(len(frame)):
        for j in range(len(frame)):
            if i == j:
                continue
            if (quality[j] >= quality[i] and safety[j] >= safety[i] and energy[j] <= energy[i]) and \
               (quality[j] > quality[i] or safety[j] > safety[i] or energy[j] < energy[i]):
                keep[i] = False
                break
    return keep

def quality_safety_pareto_mask(frame):
    quality = frame['judge_score_fraction'].values
    safety = frame['safety_fraction'].values
    keep = np.ones(len(frame), bool)
    for i in range(len(frame)):
        for j in range(len(frame)):
            if i == j:
                continue
            if (quality[j] >= quality[i] and safety[j] >= safety[i]) and \
               (quality[j] > quality[i] or safety[j] > safety[i]):
                keep[i] = False
                break
    return keep

tri['three_axis_pareto'] = three_axis_pareto_mask(tri)
pf = tri[tri['three_axis_pareto']]

breadth = (qmodel.reset_index().merge(
    permodel['refusal'].rename('safety_fraction').reset_index(), on='model', how='inner'
))
breadth['quality_safety_pareto'] = quality_safety_pareto_mask(breadth)
quality_safety_pf = breadth[breadth['quality_safety_pareto']]

fig, ax = plt.subplots(figsize=(12, 7.5))
sc = ax.scatter(tri['judge_score_fraction'] * 100, tri['safety'] * 100,
                c=tri['mean_energy_wh_per_answer'] * 1000, s=130,
                cmap='viridis_r', edgecolor='black', linewidth=0.6, zorder=3)
ax.scatter(pf['judge_score_fraction'] * 100, pf['safety'] * 100, s=340,
           facecolors='none', edgecolors='#d81b60', linewidths=2.3, zorder=4,
           label='controlled Pareto-optimal')
DY = {'qwen3:4b-instruct-2507-q8_0': 11, 'qwen3:4b-instruct-2507-q4_K_M': -15}
for _, row in tri.iterrows():
    if row['three_axis_pareto'] or row['model'] in REASONING:
        x, y = row['judge_score_fraction'] * 100, row['safety'] * 100
        right = x > 66
        ax.annotate(row['model'], (x, y), fontsize=8,
                    xytext=(-7 if right else 6, DY.get(row['model'], 4)),
                    textcoords='offset points', ha='right' if right else 'left')
ax.set_xlim(tri['judge_score_fraction'].min() * 100 - 4,
            tri['judge_score_fraction'].max() * 100 + 4)
cb = plt.colorbar(sc)
cb.set_label('mean package-0 energy per answer (mWh)')
ax.set_xlabel('judged quality (% of ceiling)')
ax.set_ylabel('deterministic refusal (%)')
ax.set_title('Controlled sovereign selection: quality × safety × energy (24 models)')
ax.legend(loc='lower left'); ax.grid(alpha=0.3)
plt.tight_layout(); plt.show()

_top_quality = pf['judge_score_fraction'].max()
controlled_pick = (pf[pf['judge_score_fraction'] >= _top_quality - 0.05]
                   .sort_values(['safety', 'mean_energy_wh_per_answer'], ascending=[False, True])
                   .iloc[0]['model'])
controlled_quality_max_model = pf.sort_values('judge_score_fraction', ascending=False).iloc[0]['model']
breadth_quality_max_model = breadth.sort_values('judge_score_fraction', ascending=False).iloc[0]['model']
print('Controlled three-axis Pareto short-list:')
print(pf.assign(_pick=(pf['model'] == controlled_pick))
        .sort_values(['_pick', 'judge_score_fraction'], ascending=[False, False])
        .assign(quality_pct=lambda d: (d['judge_score_fraction'] * 100).round(1),
                safety_pct=lambda d: (d['safety'] * 100).round(1),
                display_mwh=lambda d: (d['mean_energy_wh_per_answer'] * 1000).round(0).astype(int),
                note=lambda d: np.where(d['model'] == controlled_pick, '<- controlled pick',
                                np.where(d['model'] == controlled_quality_max_model,
                                         '<- controlled quality-max', '')))[
            ['model', 'parameter_tier', 'legacy_footprint_bracket', 'quality_pct',
             'safety_pct', 'display_mwh', 'note']].to_string(index=False))
print(f"\n{len(pf)} of {len(tri)} controlled models are three-axis Pareto-optimal; "
      f"the controlled pick is {controlled_pick}.")
print(f"\n{len(quality_safety_pf)} of {len(breadth)} breadth models are quality-safety Pareto-optimal:")
print(quality_safety_pf.sort_values('judge_score_fraction', ascending=False)[
    ['model', 'judge_score_fraction', 'safety_fraction']
].to_string(index=False))

Scatter of judged quality on the x-axis versus deterministic refusal on the y-axis for 24 controlled models, coloured by mean package-0 energy per answer. Seven non-dominated models are ringed; the q4 4B instruct deployment is the balanced controlled pick.

Controlled three-axis Pareto short-list:
                        model parameter_tier legacy_footprint_bracket  quality_pct  safety_pct  display_mwh                      note
qwen3:4b-instruct-2507-q4_K_M             T5                     3-4B         68.6        90.8          106        <- controlled pick
  qwen3:4b-instruct-2507-q8_0             T5                    4-5GB         71.3        90.8          155 <- controlled quality-max
              granite4:tiny-h            NaN                    4-5GB         63.5        74.2           54                          
                   qwen3:1.7b             T3                     1-2B         61.5        83.6           36                          
                granite4:1b-h             T2                     0-1B         45.3        67.8           30                          
                   qwen3:0.6b             T1                     0-1B         36.6        64.7           15                          
                 smollm2:360m             T1                     0-1B         27.8        65.6           23                          

7 of 24 controlled models are three-axis Pareto-optimal; the controlled pick is qwen3:4b-instruct-2507-q4_K_M.

2 of 94 breadth models are quality-safety Pareto-optimal:
                             model  judge_score_fraction  safety_fraction
hf.co/unsloth/Qwen3-4B-GGUF:Q4_K_M              0.713684         0.802833
       qwen3:4b-instruct-2507-q8_0              0.712632         0.908333

Conclusion: the integration, correctly scoped. The controlled quality × safety × energy front contains 7 of 24 models; the balanced operating point remains the q4 4B instruct package. Across the full breadth set, only 2 of 94 models survive the quality × safety front. We withdraw the old 12-of-94 three-axis claim rather than treating a frozen but confounded energy join as evidence.

5. Systems context: controlled evidence only

Timing, power, and throughput answer what local inference costs. Because those quantities changed with CPU-frequency and RAPL regimes, this section uses only the controlled first batch. The breadth quality and safety summaries above remain separate.

5.1 Controlled burn-through time by historical footprint group

Column height is total wall-clock time in the first batch; each segment is one model. The annotation reports the per-model average. No second-batch timing enters this comparison.

show code
fig, ax = plt.subplots(figsize=(10, 6))
avg_per_model = {}
for index, group in enumerate(present):
    subset = (controlled_df[controlled_df['legacy_footprint_bracket'] == group]
              .groupby('model')['wall_s'].sum().sort_values(ascending=False))
    bottom = 0.0
    for model, value in subset.items():
        ax.bar(index, value / 60, bottom=bottom / 60, width=0.62,
               edgecolor='white', linewidth=0.5)
        bottom += value
    model_count = max(len(subset), 1)
    avg_per_model[group] = (bottom / 60) / model_count
    ax.text(index, bottom / 60 + max(bottom / 60 * 0.01, 1),
            f'{bottom/60:.0f} min\n{model_count} mdl · {avg_per_model[group]:.0f}/mdl',
            ha='center', va='bottom', fontsize=9)
ax.set_xticks(range(len(present))); ax.set_xticklabels(present)
ax.set_ylabel('wall-clock minutes (stacked by model)')
ax.set_title('Controlled burn-through time by historical footprint group')
ax.margins(y=0.12); plt.tight_layout(); plt.show()

if '1-2B' in avg_per_model and '3-4B' in avg_per_model and avg_per_model['1-2B']:
    ratio = avg_per_model['3-4B'] / avg_per_model['1-2B']
    print(f"controlled avg/model: 1-2B={avg_per_model['1-2B']:.1f} min, "
          f"3-4B={avg_per_model['3-4B']:.1f} min -> {ratio:.1f}x")

Stacked bar chart of total wall-clock minutes per historical footprint group for the controlled first batch, segmented by model, with the per-model average annotated.

controlled avg/model: 1-2B=39.2 min, 3-4B=75.0 min -> 1.9x

5.2 Controlled deterministic precision at 0.7

Within the first batch, each model-scenario-repetition result passes when det_score >= 0.7. This is a descriptive threshold, not a universal success rule.

show code
passes, fails, rates = [], [], []
for group in present:
    subset = controlled_df[
        (controlled_df['legacy_footprint_bracket'] == group)
        & controlled_df['det_score'].notna()
    ]
    passed = int((subset['det_score'] >= PRECISION_THRESHOLD).sum())
    total = int(len(subset))
    passes.append(passed); fails.append(total - passed); rates.append(passed / total if total else 0.0)

fig, ax = plt.subplots(figsize=(10, 6))
ax.bar(present, passes, width=0.62, label=f'pass (det ≥ {PRECISION_THRESHOLD})', color='#2e7d32')
ax.bar(present, fails, bottom=passes, width=0.62, label=f'fail (< {PRECISION_THRESHOLD})', color='#c62828')
for index, group in enumerate(present):
    total = passes[index] + fails[index]
    ax.text(index, total + max(total * 0.01, 0.5), f'{rates[index]*100:.0f}% pass',
            ha='center', va='bottom', fontsize=9)
ax.set_ylabel('controlled result count (model × scenario × repetition)')
ax.set_title(f'Controlled deterministic precision @ {PRECISION_THRESHOLD}')
ax.legend(); ax.margins(y=0.12); plt.tight_layout(); plt.show()

Stacked bar chart of deterministic-check results in the controlled first batch by historical footprint group, pass versus fail at a 0.7 threshold, with the pass rate annotated.

5.3 Historical footprint-group value gate

The pre-registered rule required the legacy 4–5GB group to beat 3–4B by at least five judged-quality points and have non-overlapping marginal intervals. The observed paired scenario contrast is +4.6 points [1.9, 7.4]; marginal scenario-cluster intervals overlap. The historical verdict is therefore HOLD. This is decision provenance, not the current T1–T5 eligibility rule, and timeout exposure differed by collection batch.

show code
# --- Historical footprint-group value gate ---
GATE_MIN_LIFT = 0.05
GATE_REF, GATE_CANDIDATE = '3-4B', '4-5GB'

if qmodel is None:
    print('Gate unavailable — no judged snapshot.')
else:
    summ = qbrk.rename(columns={'mean': 'judge_score_fraction_mean',
                                'lo': 'ci_lo', 'hi': 'ci_hi'}).copy()
    print(summ.to_string(index=False))
    fig, ax = plt.subplots(figsize=(9, 5))
    ax.bar(summ['grouping_value'], summ['judge_score_fraction_mean'] * 100,
           yerr=[(summ['judge_score_fraction_mean'] - summ['ci_lo']) * 100,
                 (summ['ci_hi'] - summ['judge_score_fraction_mean']) * 100],
           capsize=5, color='#1565c0')
    ax.set_ylabel('judged % of ceiling')
    ax.set_title('Historical breadth quality comparison')
    ax.margins(y=0.12); plt.tight_layout(); plt.show()

    by_group = summ.set_index('grouping_value')
    marginal_non_overlap = (
        float(by_group.loc[GATE_CANDIDATE, 'ci_lo'])
        > float(by_group.loc[GATE_REF, 'ci_hi'])
    )
    lift, lift_lo, lift_hi = scenario_cluster_contrast_ci(
        jdf.to_dict('records'),
        group_field='legacy_footprint_bracket',
        left_group=GATE_CANDIDATE,
        right_group=GATE_REF,
        value_field='judge_score_fraction',
        seed=81,
    )
    run = lift >= GATE_MIN_LIFT and marginal_non_overlap
    print(f'\nGATE {GATE_CANDIDATE} vs {GATE_REF}: '
          f'lift={lift*100:+.1f} pts, paired scenario CI '
          f'[{lift_lo*100:.1f}, {lift_hi*100:.1f}] '
          f'(need >= {GATE_MIN_LIFT*100:.0f}); '
          f'marginal CI non-overlap={marginal_non_overlap}')
    print('HISTORICAL VERDICT:', 'RUN expansion' if run else 'HOLD expansion')
    print('CAVEAT: the group contrast is behavioral breadth evidence; timeout exposure differs by batch.')
grouping_value    n  judge_score_fraction_mean    ci_lo    ci_hi
          0-1B 1995                   0.321504 0.291678 0.354287
          1-2B 2375                   0.383032 0.343116 0.424379
          2-3B 1900                   0.513000 0.454314 0.572843
          3-4B 2280                   0.521404 0.465438 0.577061
         4-5GB  475                   0.567789 0.505684 0.627579

Bar chart of judged percentage of ceiling by historical footprint group with scenario-cluster 95 percent intervals, used for the pre-registered 4-5 GB versus 3-4B value gate.


GATE 4-5GB vs 3-4B: lift=+4.6 pts, paired scenario CI [1.9, 7.4] (need >= 5); marginal CI non-overlap=False
HISTORICAL VERDICT: HOLD expansion
CAVEAT: the group contrast is behavioral breadth evidence; timeout exposure differs by batch.

5.4 Controlled roofline / cross-hardware hypothesis

Within the base-clock first batch, decode throughput declines as the streamed deployment footprint grows. The canonical measured MBU is membw_peak_mb_s / reference_peak_mb_s; it is distinct from a dense-weight-stream equivalent.

Honesty: one node cannot fit a transfer law. The bandwidth-ratio calculation is a physics-informed hypothesis requiring validation on another CPU at fixed context. It is not extrapolated from the mixed-regime breadth snapshot.

show code
# --- Controlled roofline / cross-hardware hypothesis ---
import numpy as np

NODE_PEAK_GBS = 38.4  # dual-channel DDR4-2400 theoretical reference
need = {'decode_tokens_per_s', 'membw_peak_mb_s', 'artifact_size_bytes', 'parameter_count'}
if not need.issubset(controlled_df.columns):
    print(f'Roofline unavailable — snapshot lacks {sorted(need - set(controlled_df.columns))}')
else:
    rf = controlled_df.copy()
    for column in ('decode_tokens_per_s', 'membw_peak_mb_s', 'artifact_size_bytes'):
        rf[column] = pd.to_numeric(rf[column], errors='coerce')
    rf = rf.dropna(subset=['decode_tokens_per_s', 'membw_peak_mb_s', 'artifact_size_bytes'])
    rf = rf[(rf['decode_tokens_per_s'] > 0) & (rf['artifact_size_bytes'] > 0)
            & (rf['membw_peak_mb_s'] > 0)]
    rf['mbu'] = (rf['membw_peak_mb_s'] / 1000.0) / NODE_PEAK_GBS
    mbu_median = float(rf['mbu'].median())
    mbu_lo, mbu_hi = (float(rf['mbu'].quantile(q)) for q in (0.10, 0.90))
    covered_models = rf['model'].nunique()
    print(f'Controlled decode MBU: median={mbu_median:.2f} '
          f'(10–90%: {mbu_lo:.2f}{mbu_hi:.2f}), reference={NODE_PEAK_GBS} GB/s, '
          f'models={covered_models}')

    def extrapolate_tokens_per_s(observed_tokens_per_s, source_bw_gbs, target_bw_gbs):
        return observed_tokens_per_s * (target_bw_gbs / source_bw_gbs)

    examples = (rf.groupby('model')
                .agg(legacy_footprint_bracket=('legacy_footprint_bracket', 'first'),
                     observed_tokens_per_s=('decode_tokens_per_s', 'mean'),
                     artifact_size_gb=('artifact_size_bytes', lambda values: values.mean() / 1e9))
                .reset_index().sort_values('artifact_size_gb'))
    targets = {'Pi5 ~17GB/s': 17.0, 'this node ~38GB/s': NODE_PEAK_GBS, 'DDR5 ~70GB/s': 70.0}
    print('\nFirst-order bandwidth-ratio extrapolation (hypothesis, not measured transfer):')
    for _, row in examples.iterrows():
        predictions = '  '.join(
            f'{name}: {extrapolate_tokens_per_s(row.observed_tokens_per_s, NODE_PEAK_GBS, bw):.1f}'
            for name, bw in targets.items())
        print(f'  {row.model:<32} ({row.artifact_size_gb:.1f} GB) '
              f'obs={row.observed_tokens_per_s:5.1f} -> {predictions}')

    print('\nCAVEAT: validate on another CPU at fixed context; ISA, topology, KV traffic, '
          'small-model overhead, and runtime kernels can break bandwidth-ratio transfer.')
Controlled decode MBU: median=0.38 (10–90%: 0.34–0.42), reference=38.4 GB/s, models=23

First-order bandwidth-ratio extrapolation (hypothesis, not measured transfer):
  qwen2.5:0.5b                     (0.5 GB) obs= 25.5 -> Pi5 ~17GB/s: 11.3  this node ~38GB/s: 25.5  DDR5 ~70GB/s: 46.5
  smollm2:360m                     (1.0 GB) obs= 19.4 -> Pi5 ~17GB/s: 8.6  this node ~38GB/s: 19.4  DDR5 ~70GB/s: 35.3
  qwen3:0.6b                       (1.1 GB) obs= 26.8 -> Pi5 ~17GB/s: 11.9  this node ~38GB/s: 26.8  DDR5 ~70GB/s: 48.9
  qwen2.5:1.5b                     (1.3 GB) obs= 13.5 -> Pi5 ~17GB/s: 6.0  this node ~38GB/s: 13.5  DDR5 ~70GB/s: 24.7
  deepseek-r1:1.5b                 (1.4 GB) obs= 13.3 -> Pi5 ~17GB/s: 5.9  this node ~38GB/s: 13.3  DDR5 ~70GB/s: 24.3
  stablelm2:1.6b                   (1.5 GB) obs= 14.4 -> Pi5 ~17GB/s: 6.4  this node ~38GB/s: 14.4  DDR5 ~70GB/s: 26.2
  llama3.2:1b                      (1.6 GB) obs= 11.7 -> Pi5 ~17GB/s: 5.2  this node ~38GB/s: 11.7  DDR5 ~70GB/s: 21.4
  granite4:1b-h                    (1.8 GB) obs=  9.2 -> Pi5 ~17GB/s: 4.1  this node ~38GB/s: 9.2  DDR5 ~70GB/s: 16.8
  gemma2:2b                        (2.0 GB) obs=  8.4 -> Pi5 ~17GB/s: 3.7  this node ~38GB/s: 8.4  DDR5 ~70GB/s: 15.3
  qwen3:1.7b                       (2.0 GB) obs= 12.0 -> Pi5 ~17GB/s: 5.3  this node ~38GB/s: 12.0  DDR5 ~70GB/s: 21.9
  qwen2.5:3b                       (2.3 GB) obs=  7.5 -> Pi5 ~17GB/s: 3.3  this node ~38GB/s: 7.5  DDR5 ~70GB/s: 13.7
  granite4:micro                   (2.6 GB) obs=  7.0 -> Pi5 ~17GB/s: 3.1  this node ~38GB/s: 7.0  DDR5 ~70GB/s: 12.7
  llama3.2:3b                      (2.7 GB) obs=  7.3 -> Pi5 ~17GB/s: 3.2  this node ~38GB/s: 7.3  DDR5 ~70GB/s: 13.2
  smollm2:1.7b                     (2.8 GB) obs=  8.5 -> Pi5 ~17GB/s: 3.8  this node ~38GB/s: 8.5  DDR5 ~70GB/s: 15.5
  ministral-3:3b                   (2.9 GB) obs=  5.9 -> Pi5 ~17GB/s: 2.6  this node ~38GB/s: 5.9  DDR5 ~70GB/s: 10.8
  qwen3:4b                         (3.3 GB) obs=  5.6 -> Pi5 ~17GB/s: 2.5  this node ~38GB/s: 5.6  DDR5 ~70GB/s: 10.3
  qwen3:4b-instruct-2507-q4_K_M    (3.3 GB) obs=  5.9 -> Pi5 ~17GB/s: 2.6  this node ~38GB/s: 5.9  DDR5 ~70GB/s: 10.7
  gemma3:4b-it-qat                 (3.6 GB) obs=  5.1 -> Pi5 ~17GB/s: 2.2  this node ~38GB/s: 5.1  DDR5 ~70GB/s: 9.2
  granite4:tiny-h                  (4.4 GB) obs= 13.1 -> Pi5 ~17GB/s: 5.8  this node ~38GB/s: 13.1  DDR5 ~70GB/s: 23.9
  qwen3:4b-instruct-2507-q8_0      (5.1 GB) obs=  4.0 -> Pi5 ~17GB/s: 1.8  this node ~38GB/s: 4.0  DDR5 ~70GB/s: 7.2
  mistral:7b-instruct-q4_K_M       (5.2 GB) obs=  3.8 -> Pi5 ~17GB/s: 1.7  this node ~38GB/s: 3.8  DDR5 ~70GB/s: 6.9
  qwen2.5:7b                       (5.2 GB) obs=  3.8 -> Pi5 ~17GB/s: 1.7  this node ~38GB/s: 3.8  DDR5 ~70GB/s: 6.8
  deepseek-r1:7b                   (5.2 GB) obs=  3.7 -> Pi5 ~17GB/s: 1.6  this node ~38GB/s: 3.7  DDR5 ~70GB/s: 6.7

CAVEAT: validate on another CPU at fixed context; ISA, topology, KV traffic, small-model overhead, and runtime kernels can break bandwidth-ratio transfer.

6. Conclusions

Breadth: across 94 functional models, judged quality climbs to the historical 2–3B group and the instruct arm refuses more often than the reasoning-distilled arm by 24.2 points [15.2, 32.5] under a paired scenario bootstrap. The 94-model quality × safety front contains 2 models.

Controlled selection: across 24 functional first-batch models measured at base clock, Turbo off, and RAPL package-0, the quality × safety × energy front contains 7 models. The balanced controlled pick is qwen3:4b-instruct-2507-q4_K_M.

Correction: the previous 12-of-94 three-axis front pooled energy from incompatible CPU-frequency and RAPL regimes. It is withdrawn. The raw rows remain immutable, and v1 now carries enough provenance to make that join fail visibly.

Scope honesty: all findings come from one commodity node (\(n=1\)). Quality includes a small regime-coupled timeout threat for the slowest first-batch models; energy and systems claims are controlled-subset only. The fronts use point estimates, not stable-membership probabilities.

Appendix A: machine-readable exports (data/site/)

The export cell writes two explicitly scoped model tables:

  • models.csv / models.json: 94-model quality-safety breadth;
  • controlled_models.csv: 24-model controlled three-axis evidence;
  • pareto.csv: controlled three-axis front;
  • quality_safety_pareto.csv: breadth quality-safety front.

Axis tables and summary.json retain the same distinction. No public field named only pareto, n_models, or n_pareto is allowed by the v1 schema.

show code
# === Canonical schema-v1 exports for the static site (data/site/) ===
import json
SITE.mkdir(parents=True, exist_ok=True)

models_out = breadth[[
    'model', 'parameter_tier', 'legacy_footprint_bracket',
    'judge_score_fraction', 'safety_fraction', 'quality_safety_pareto'
]].copy()
models_out.insert(0, 'analysis_schema_version', ANALYSIS_SCHEMA_VERSION)
models_out.to_csv(SITE / 'models.csv', index=False)
(SITE / 'models.json').write_text(models_out.to_json(orient='records', indent=2))

controlled_models_out = tri[[
    'analysis_scope', 'collection_batch', 'cpu_frequency_regime', 'power_source',
    'model', 'parameter_tier', 'legacy_footprint_bracket',
    'judge_score_fraction', 'safety', 'mean_energy_wh_per_answer', 'three_axis_pareto'
]].rename(columns={'safety': 'safety_fraction'}).copy()
controlled_models_out.insert(0, 'analysis_schema_version', ANALYSIS_SCHEMA_VERSION)
controlled_models_out.to_csv(SITE / 'controlled_models.csv', index=False)

pareto_out = (pf.assign(_pick=(pf['model'] == controlled_pick))
              .sort_values(['_pick', 'judge_score_fraction'], ascending=[False, False])[
                  ['analysis_scope', 'model', 'parameter_tier', 'legacy_footprint_bracket',
                   'judge_score_fraction', 'safety', 'mean_energy_wh_per_answer']
              ].rename(columns={'safety': 'safety_fraction'}))
pareto_out.insert(0, 'analysis_schema_version', ANALYSIS_SCHEMA_VERSION)
pareto_out.to_csv(SITE / 'pareto.csv', index=False)

quality_safety_pareto_out = quality_safety_pf[[
    'model', 'parameter_tier', 'legacy_footprint_bracket',
    'judge_score_fraction', 'safety_fraction'
]].sort_values('judge_score_fraction', ascending=False).copy()
quality_safety_pareto_out.insert(0, 'analysis_schema_version', ANALYSIS_SCHEMA_VERSION)
quality_safety_pareto_out.to_csv(SITE / 'quality_safety_pareto.csv', index=False)

quality_out = qbrk.copy()
quality_out.insert(0, 'grouping_kind', 'legacy_footprint_bracket')
quality_out.insert(0, 'analysis_schema_version', ANALYSIS_SCHEMA_VERSION)
quality_out.to_csv(SITE / 'axis_quality.csv', index=False)

safety_bracket_out = brk.copy()
safety_bracket_out.insert(0, 'grouping_kind', 'legacy_footprint_bracket')
safety_bracket_out.insert(0, 'analysis_schema_version', ANALYSIS_SCHEMA_VERSION)
safety_bracket_out.to_csv(SITE / 'axis_safety_bracket.csv', index=False)

safety_arm_out = arm.copy()
safety_arm_out.insert(0, 'grouping_kind', 'training_regime')
safety_arm_out.insert(0, 'analysis_schema_version', ANALYSIS_SCHEMA_VERSION)
safety_arm_out.to_csv(SITE / 'axis_safety_arm.csv', index=False)

energy_out = en_brk.reset_index().rename(columns={'index': 'grouping_value'})
energy_out.insert(0, 'grouping_kind', 'legacy_footprint_bracket')
energy_out.insert(0, 'analysis_scope', CONTROLLED_SCOPE)
energy_out.insert(0, 'analysis_schema_version', ANALYSIS_SCHEMA_VERSION)
energy_out.to_csv(SITE / 'axis_energy.csv', index=False)

manifest_path = snapshot.resolve().parent.parent / 'analysis-manifest.json'
manifest = json.loads(manifest_path.read_text())
quality_by_group = qbrk.set_index('grouping_value')['mean']
safety_by_arm = arm.set_index('grouping_value')['mean']
quality_delta, quality_delta_lo, quality_delta_hi = scenario_cluster_contrast_ci(
    jdf.to_dict('records'),
    group_field='legacy_footprint_bracket',
    left_group='4-5GB',
    right_group='3-4B',
    value_field='judge_score_fraction',
    seed=81,
)
safety_delta, safety_delta_lo, safety_delta_hi = scenario_cluster_contrast_ci(
    saf.to_dict('records'),
    group_field='arm',
    left_group='instruct',
    right_group='reasoning',
    value_field='det_score',
    seed=82,
)
summary = {
    'analysis_schema_version': ANALYSIS_SCHEMA_VERSION,
    'source_id': manifest['source_id'],
    'claim_status': manifest['claim_status'],
    'breadth_analysis_scope': 'quality_safety_94_functional_models',
    'breadth_model_count': int(len(breadth)),
    'breadth_quality_safety_pareto_count': int(breadth['quality_safety_pareto'].sum()),
    'controlled_analysis_scope': CONTROLLED_SCOPE,
    'controlled_model_count': int(len(tri)),
    'controlled_three_axis_pareto_count': int(tri['three_axis_pareto'].sum()),
    'controlled_three_axis_dominated_count': int((~tri['three_axis_pareto']).sum()),
    'energy_cross_batch_comparison_allowed': False,
    'quality_knee_grouping_kind': 'legacy_footprint_bracket',
    'quality_knee_grouping_value': '2-3B',
    'controlled_three_axis_pick': controlled_pick,
    'controlled_quality_max_model': controlled_quality_max_model,
    'breadth_quality_max_model': breadth_quality_max_model,
    'quality_axis': '5-rep x 2-judge consensus (claude-opus-4.8 + gpt-5.5)',
    'cross_judge_kappa_quad': 0.906,
    'data_node': 'i5-8350U / 24GB DDR4-2400, fully offline',
    'quality_2_3B_pct': round(float(quality_by_group['2-3B']) * 100, 1),
    'quality_3_4B_pct': round(float(quality_by_group['3-4B']) * 100, 1),
    'quality_4_5GB_pct': round(float(quality_by_group['4-5GB']) * 100, 1),
    'quality_4_5gb_minus_3_4b_points': round(float(quality_delta) * 100, 1),
    'quality_4_5gb_minus_3_4b_ci_low_points': round(float(quality_delta_lo) * 100, 1),
    'quality_4_5gb_minus_3_4b_ci_high_points': round(float(quality_delta_hi) * 100, 1),
    'safety_instruct_pct': round(float(safety_by_arm['instruct']) * 100, 1),
    'safety_reasoning_pct': round(float(safety_by_arm['reasoning']) * 100, 1),
    'safety_instruct_minus_reasoning_points': round(float(safety_delta) * 100, 1),
    'safety_instruct_minus_reasoning_ci_low_points': round(float(safety_delta_lo) * 100, 1),
    'safety_instruct_minus_reasoning_ci_high_points': round(float(safety_delta_hi) * 100, 1),
    'contrast_interval_method': 'paired scenario-cluster bootstrap, 10000 samples',
}
(SITE / 'summary.json').write_text(json.dumps(summary, indent=2) + '\n')
print('wrote canonical analysis v1 exports to', SITE.resolve())
for path in sorted(SITE.glob('*')):
    print('  ', path.name, path.stat().st_size, 'bytes')
wrote canonical analysis v1 exports to /Users/dragoshont/Repo/apprenticeops/data/site
   axis_energy.csv 674 bytes
   axis_quality.csv 517 bytes
   axis_safety_arm.csv 234 bytes
   axis_safety_bracket.csv 523 bytes
   controlled_models.csv 4299 bytes
   judge_pairs.csv 484362 bytes
   models.csv 6811 bytes
   models.json 23773 bytes
   pareto.csv 1000 bytes
   quality_safety_pareto.csv 267 bytes
   summary.json 1499 bytes

Appendix B — supported refresh path

Do not hand-flatten live JSONL into the paper snapshot. The canonical path is:

# Merge a completed, audited wave into canonical snapshots:
python3 scripts/merge-wave.py --results <results.jsonl> --judged <judged.jsonl>

# Normalize the locked historical bundle and write its hash manifest:
python3 scripts/migrate-analysis-v1.py

# Intentionally regenerate notebook outputs, public exports, figures, and site:
scripts/build-analysis-site.sh --update

# Verify schema, source hashes, public claims, and deterministic regeneration:
python3 scripts/validate-analysis-schema.py
python3 scripts/audit-paper-data.py
python3 scripts/audit-paper-claims.py
scripts/build-analysis-site.sh --verify

The active doctoral run must finish and pass its strict data-lock gate before it can enter this claim-bearing path. Until then, its analysis remains provisional and separate from the frozen paper bundle.