"""DER on authored integer-second bins, two speakers, no overlap/collar."""
from fractions import Fraction as F
import itertools,json
REF=['A']*4+['B']*4+[None]*2
HYP=['x']*3+['y']*4+[None,'x',None]
def evaluate(ref,hyp):
    if len(ref)!=len(hyp):raise ValueError('same evaluation bins required')
    labels=sorted({h for h in hyp if h is not None});speakers=sorted({r for r in ref if r is not None})
    if len(labels)!=2 or len(speakers)!=2:raise ValueError('this exercise requires exactly two labels each')
    total=sum(r is not None for r in ref)
    options=[]
    for permutation in itertools.permutations(speakers):
        mapping=dict(zip(labels,permutation));counts={'miss':0,'false_alarm':0,'confusion':0};timeline=[]
        for i,(r,h) in enumerate(zip(ref,hyp)):
            kind='correct'
            if r is not None and h is None:kind='miss'
            elif r is None and h is not None:kind='false_alarm'
            elif r is not None and h is not None and r!=mapping[h]:kind='confusion'
            if kind in counts:counts[kind]+=1
            timeline.append({'start':i,'end_exclusive':i+1,'reference':r,'hypothesis':h,'mapped':mapping.get(h),'status':kind})
        options.append({'mapping':mapping,'counts':counts,'total_reference_speaker_seconds':total,'DER_fraction':str(F(sum(counts.values()),total)),'DER':sum(counts.values())/total,'timeline':timeline})
    return min(options,key=lambda o:o['counts']['confusion'])
if __name__=='__main__':
    a=evaluate(REF,HYP)
    assert a['mapping']=={'x':'A','y':'B'} and a['counts']=={'miss':1,'false_alarm':1,'confusion':1}
    assert a['DER']==.375 and a['total_reference_speaker_seconds']==8
    renamed=[{'x':'y','y':'x'}.get(h) for h in HYP]
    assert evaluate(REF,renamed)['DER']==a['DER']
    print(json.dumps(a,ensure_ascii=False,indent=2))
