# === 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')