diff --git a/flares.py b/flares.py index 9a365fa..0f06a99 100644 --- a/flares.py +++ b/flares.py @@ -6851,7 +6851,9 @@ def sparks_csv_export( haemo_obj: BaseRaw, save_path: str, ) -> None: - + """Export sparks data to CSV with SHA256 checksum verification (Issue #106).""" + import hashlib + raw = haemo_obj data, times = raw.get_data(return_times=True) ann_col = np.full(times.shape, "", dtype=object) @@ -6868,7 +6870,26 @@ def sparks_csv_export( df = pd.DataFrame(data.T, columns=raw.ch_names) df.insert(0, "annotation", ann_col) df.insert(0, "time", times) - df.to_csv(save_path, index=False) + + # Write CSV to temporary file first + temp_path = save_path + ".tmp" + df.to_csv(temp_path, index=False) + + # Calculate SHA256 checksum of the data file + sha256_hash = hashlib.sha256() + with open(temp_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + sha256_hash.update(chunk) + checksum_hex = sha256_hash.hexdigest() + + # Append checksum to end of file + with open(temp_path, "a") as f: + f.write(f"\n# SHA256: {checksum_hex}") + + # Atomic rename + import os + os.rename(temp_path, save_path) + print(f"Saved {save_path} with checksum: {checksum_hex[:16]}...") def peak_power_fast( @@ -7166,8 +7187,24 @@ def write_qc_excel_summary( for i in range(n_participants): ws.column_dimensions[get_column_letter(i + 2)].width = 14 ws.freeze_panes = "B2" - - wb.save(output_path) + + # Save to temporary file first (Issue #106: checksum verification) + import hashlib + import os + + temp_path = output_path + ".tmp" + wb.save(temp_path) + + # Calculate SHA256 checksum + sha256_hash = hashlib.sha256() + with open(temp_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + sha256_hash.update(chunk) + checksum_hex = sha256_hash.hexdigest() + + # Atomic rename + os.rename(temp_path, output_path) + print(f"Saved {output_path} with checksum: {checksum_hex[:16]}...") def plot_and_enqueue_waveform_metrics(