#!/usr/bin/env python3 """Audit explicit synthetic/research split IDs. Does not inspect EEG signals.""" import argparse,csv,json,sys VERSION='1.0.0' FIELDS=['fold','partition','participant','session','trial','window'] def audit(rows,group='participant'): if group not in {'participant','session','trial'}:raise ValueError('Unknown grouping unit') if not rows:raise ValueError('Empty manifest') groups={};windows={};length={'participant':1,'session':2,'trial':3}[group] for n,row in enumerate(rows,2): row={k:str(row.get(k) or '').strip() for k in FIELDS} if any(not str(row.get(k,'')).strip() for k in FIELDS):raise ValueError('Missing identifier at CSV line '+str(n)) if row['partition'] not in {'train','validation','test'}:raise ValueError('Unknown partition at CSV line '+str(n)) identity=tuple(row[k] for k in ['participant','session','trial','window']) groups.setdefault((row['fold'],identity[:length]),set()).add(row['partition']) windows.setdefault((row['fold'],identity),set()).add(row['partition']) findings=[] for label,values in [(group,groups),('window',windows)]: for (fold,identity),partitions in sorted(values.items()): if len(partitions)>1:findings.append({'fold':fold,'unit':label,'id':list(identity),'partitions':sorted(partitions)}) return {'version':VERSION,'group':group,'rows':len(rows),'findings':findings,'limitation':'Checks supplied IDs only; does not certify a leakage-free analysis.'} def main(): p=argparse.ArgumentParser(description=__doc__);p.add_argument('manifest');p.add_argument('--group',choices=['participant','session','trial'],default='participant');a=p.parse_args() try: with open(a.manifest,newline='',encoding='utf-8-sig') as f:result=audit(list(csv.DictReader(f)),a.group) except (OSError,ValueError) as e:print(str(e),file=sys.stderr);return 2 print(json.dumps(result,indent=2));return 1 if result['findings'] else 0 if __name__=='__main__':sys.exit(main())