82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
"""
|
|
Validation utilities for figure quality control
|
|
"""
|
|
|
|
import os
|
|
import numpy as np
|
|
from scipy import stats
|
|
|
|
def validate_figure_output(fig_num, output_files, computed_metrics, validation_flags, config):
|
|
"""
|
|
Validate figure output against quality thresholds
|
|
|
|
Args:
|
|
fig_num: Figure number (1-15)
|
|
output_files: List of generated file paths
|
|
computed_metrics: Dictionary of computed metrics
|
|
validation_flags: Dictionary of boolean validation flags
|
|
config: Configuration dictionary with validation thresholds
|
|
|
|
Returns:
|
|
dict: Validation result with pass/fail status
|
|
"""
|
|
|
|
result = {
|
|
"figure": f"Fig{fig_num:02d}",
|
|
"output_files": output_files,
|
|
"metrics": computed_metrics,
|
|
"flags": validation_flags,
|
|
"pass": True,
|
|
"errors": []
|
|
}
|
|
|
|
# Check file existence
|
|
for filepath in output_files:
|
|
if not os.path.exists(filepath):
|
|
result["pass"] = False
|
|
result["errors"].append(f"File not found: {filepath}")
|
|
elif os.path.getsize(filepath) == 0:
|
|
result["pass"] = False
|
|
result["errors"].append(f"Empty file: {filepath}")
|
|
|
|
# Figure-specific validation
|
|
validation_thresholds = config.get('validation', {})
|
|
|
|
if fig_num == 3: # OCV fitting
|
|
r2 = computed_metrics.get('r2', 0)
|
|
r2_min = validation_thresholds.get('fig03_r2_min', 0.99)
|
|
if r2 < r2_min:
|
|
result["pass"] = False
|
|
result["errors"].append(f"R² too low: {r2:.4f} < {r2_min}")
|
|
|
|
elif fig_num == 7: # Baseline validation
|
|
v_i_corr = computed_metrics.get('v_i_correlation', 0)
|
|
corr_max = validation_thresholds.get('fig07_v_i_corr_max', -0.5)
|
|
if v_i_corr > corr_max:
|
|
result["pass"] = False
|
|
result["errors"].append(f"V-I correlation not negative enough: {v_i_corr:.3f} > {corr_max}")
|
|
|
|
elif fig_num == 9: # Scenario comparison
|
|
if 'delta_tte_match' in validation_flags:
|
|
if not validation_flags['delta_tte_match']:
|
|
result["pass"] = False
|
|
result["errors"].append("ΔTTE annotation mismatch")
|
|
|
|
elif fig_num == 13: # Survival curve
|
|
if 'survival_monotonic' in validation_flags:
|
|
if not validation_flags['survival_monotonic']:
|
|
result["pass"] = False
|
|
result["errors"].append("Survival curve not monotonic")
|
|
|
|
return result
|
|
|
|
def compute_r2(y_true, y_pred):
|
|
"""Compute R-squared coefficient of determination"""
|
|
ss_res = np.sum((y_true - y_pred) ** 2)
|
|
ss_tot = np.sum((y_true - np.mean(y_true)) ** 2)
|
|
return 1 - (ss_res / ss_tot)
|
|
|
|
def check_monotonic_decreasing(arr):
|
|
"""Check if array is monotonically decreasing"""
|
|
return np.all(np.diff(arr) <= 0)
|