"""Authored workshop-kit return times, right censoring and exact product-limit estimate."""
from fractions import Fraction as F
import json
ROWS=[{'id':'K1','duration':1,'event':1},{'id':'K2','duration':2,'event':0},{'id':'K3','duration':2,'event':1},{'id':'K4','duration':4,'event':0},{'id':'K5','duration':5,'event':1},{'id':'K6','duration':6,'event':0}]
def curve(rows):
    if not rows or len({r['id'] for r in rows})!=len(rows):raise ValueError('nonempty unique ids')
    if any(type(r['duration']) is not int or r['duration']<=0 or type(r['event']) is not int or r['event'] not in [0,1] for r in rows):raise ValueError('positive integer time and event flag')
    survival=F(1);out=[]
    for t in sorted({r['duration'] for r in rows}):
        risk=sum(r['duration']>=t for r in rows);events=sum(r['duration']==t and r['event']==1 for r in rows);censors=sum(r['duration']==t and r['event']==0 for r in rows)
        survival*=F(risk-events,risk)
        out.append({'time':t,'at_risk_before':risk,'events':events,'censors':censors,'survival':str(survival)})
    median=next((r['time'] for r in out if F(r['survival'])<=F(1,2)),None)
    return {'table':out,'median_within_observed_range':median}
def demo():
    km=curve(ROWS);assert [r['survival'] for r in km['table']]==['5/6','2/3','2/3','1/3','1/3'];assert km['median_within_observed_range']==5
    deleted=curve([r for r in ROWS if r['event']]);all_censored=curve([dict(r,event=0) for r in ROWS]);assert all_censored['median_within_observed_range'] is None
    assert km['table'][1]=={'time':2,'at_risk_before':5,'events':1,'censors':1,'survival':'2/3'}
    assert F(deleted['table'][1]['survival'])==F(1,3)
    return {'scenario':'fictional workshop kits: first recorded return event; start time zero for all','unit':'elapsed days','administrative_cutoff':6,'rows':ROWS,'censor_reason':{'K2':'observation stops at day 2 without recorded return','K4':'observation stops at day 4 without recorded return','K6':'administrative cutoff day 6 without recorded return'},'tie_convention':'events use all still observed immediately before time; same-time censors leave after event denominator','kaplan_meier':km,'wrong_complete_case_deletion':deleted,'all_censored_counterexample':all_censored,'lifelines_executed':False,'independent_censoring_proven':False,'clinical_data':False}
if __name__=='__main__':print(json.dumps(demo(),ensure_ascii=False,indent=2))
