#!/usr/bin/env python3
"""Validate selected published Table 15 aliases against GEO sample metadata.
Outputs counts only; no GSM/sample identifiers or donor codes are exported."""
import csv, gzip, json, re, hashlib
from pathlib import Path
import zipfile, xml.etree.ElementTree as ET

D=Path(__file__).resolve().parent
SOFT=D/'GSE325735_family.soft.gz'
COUNTS=D/'GSE325735_human_raw_counts.txt.gz'
BOOK=D/'43587_2026_1234_MOESM15_ESM.xlsx'
NS={'m':'http://schemas.openxmlformats.org/spreadsheetml/2006/main','r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}

def rows_from_xlsx(path,idx):
 z=zipfile.ZipFile(path)
 sr=ET.fromstring(z.read('xl/sharedStrings.xml'))
 ss=[''.join(t.text or '' for t in x.findall('.//m:t',NS)) for x in sr.findall('m:si',NS)]
 wb=ET.fromstring(z.read('xl/workbook.xml')); rel=ET.fromstring(z.read('xl/_rels/workbook.xml.rels'))
 rm={x.attrib['Id']:x.attrib['Target'] for x in rel}
 sh=wb.findall('.//m:sheet',NS)[idx-1]; target=rm[sh.attrib['{'+NS['r']+'}id']]
 if not target.startswith('/'): target='xl/'+target
 rt=ET.fromstring(z.read(target)); out=[]
 for row in rt.findall('.//m:sheetData/m:row',NS):
  a=[]
  for c in row.findall('m:c',NS):
   v=c.find('m:v',NS)
   if v is None: val=''
   elif c.attrib.get('t')=='s': val=ss[int(v.text)]
   else: val=v.text
   a.append(val)
  out.append(a)
 return out

records=[]; cur=None
for line in gzip.open(SOFT,'rt',errors='replace'):
 if line.startswith('^SAMPLE = '):
  if cur: records.append(cur)
  cur={'title':'','ch':{}}
 elif cur is not None:
  if line.startswith('!Sample_title = '): cur['title']=line.split('=',1)[1].strip()
  elif line.startswith('!Sample_characteristics_ch1 = '):
   x=line.split('=',1)[1].strip()
   if ': ' in x:
    k,v=x.split(': ',1); cur['ch'][k]=v
if cur: records.append(cur)

with gzip.open(COUNTS,'rt') as f:
 header=next(csv.reader(f,delimiter='\t'))
aliases=set(header[1:])
tab=rows_from_xlsx(BOOK,1)
h=tab[0]; ix={k:i for i,k in enumerate(h)}
selected=[]
for r in tab[1:]:
 if len(r)<=max(ix[k] for k in ['SampleID','Replicate','Gene','Line','Passage','Dox']): continue
 if r[ix['Dox']]!='2000' or r[ix['Gene']] not in ('SRSF1','BFP'): continue
 sid=r[ix['SampleID']]
 if sid not in aliases: continue
 expected='SRSF1' if r[ix['Gene']]=='SRSF1' else 'mTagBFP2'
 mline=r[ix['Line']]; mpass=r[ix['Passage']]; mrep=r[ix['Replicate']]
 candidates=0
 for rec in records:
  ch=rec['ch']; p=re.search(r', P([0-9]+),',rec['title']); rp=re.search(r'Rep([0-9]+)',rec['title'])
  if ch.get('cell line')==mline and ch.get('genotype')==expected and ch.get('treatment')=='Dox_2000ng/mL, 3days' and p and p.group(1)==mpass and rp and rp.group(1)==mrep:
   candidates+=1
 selected.append(candidates)

out={
 'dataset':'GSE325735','condition':'Dox 2000 ng/mL for 3 days; SRSF1 vs mTagBFP2/BFP control',
 'count_matrix_aliases_present':len(selected),
 'unique_GEO_metadata_matches':sum(x==1 for x in selected),
 'ambiguous_GEO_metadata_matches':sum(x>1 for x in selected),
 'unmatched_GEO_metadata_aliases':sum(x==0 for x in selected),
 'validation':'PASS' if len(selected)==24 and all(x==1 for x in selected) else 'FAIL',
 'privacy':'No GSM, SampleID, donor code or row-level crosswalk is exported.'
}
p=D/'mapping_qc_public.json'; p.write_text(json.dumps(out,indent=2)+'\n')
print(json.dumps(out,indent=2))
