"""One SMOTE-style numeric interpolation with explicit training-only lineage."""
from fractions import Fraction as F
import hashlib,json
DATA=[{'id':'A','xy':[1,1],'label':'minor','split':'train'},{'id':'B','xy':[3,3],'label':'minor','split':'train'},{'id':'C','xy':[1,5],'label':'minor','split':'train'}]+[{'id':'M'+str(i),'xy':xy,'label':'major','split':'train'} for i,xy in enumerate([[9,9],[10,9],[9,10],[10,10]],1)]+[{'id':'T','xy':[F(5,4),F(5,4)],'label':'minor','split':'test'}]
def distance2(a,b):return sum((F(x)-F(y))**2 for x,y in zip(a,b))
def nearest(seed):
    candidates=[r for r in DATA if r['split']=='train' and r['label']==seed['label'] and r['id']!=seed['id']]
    if not candidates:raise ValueError('training class neighbor required')
    return min(candidates,key=lambda r:(distance2(seed['xy'],r['xy']),r['id']))
def interpolate(a,b,lam):
    if a['split']!='train' or b['split']!='train' or a['label']!=b['label'] or a['id']==b['id']:raise ValueError('two distinct same-class training parents')
    if not isinstance(lam,F) or not 0<=lam<=1:raise ValueError('Fraction lambda in [0,1]')
    if len(a['xy'])!=2 or len(b['xy'])!=2:raise ValueError('two numeric dimensions here')
    xy=[F(x)+lam*(F(y)-F(x)) for x,y in zip(a['xy'],b['xy'])]
    return {'parent_ids':[a['id'],b['id']],'lambda':str(lam),'xy':[str(x) for x in xy],'label':a['label'],'split':'train_synthetic'}
def test_hash():return hashlib.sha256(json.dumps([r for r in DATA if r['split']=='test'],sort_keys=True,default=str).encode()).hexdigest()
if __name__=='__main__':
    before=test_hash();a=DATA[0];b=nearest(a);sample=interpolate(a,b,F(1,4))
    assert b['id']=='B' and sample['xy']==['3/2','3/2']
    assert distance2(a['xy'],DATA[-1]['xy'])<distance2(a['xy'],b['xy'])
    assert before==test_hash()
    rejected=0
    for first,second,lam in [(a,DATA[-1],F(1,4)),(a,DATA[3],F(1,4)),(a,b,F(-1,4)),(a,b,F(5,4)),(a,a,F(1,4))]:
        try:interpolate(first,second,lam)
        except ValueError:rejected+=1
    assert rejected==5
    print(json.dumps({'selected_neighbor':b['id'],'synthetic_sample':sample,'test_hash_before':before,'test_hash_after':test_hash(),'rejected_cases':rejected,'imblearn_run':False,'classifier_trained':False},indent=2))
