            accepted=[r for r in data['orders'] if r['state'] not in output['exclude_statuses']];first={}
            for r in accepted:first[r['customer']]=min(first.get(r['customer'],r['month']),r['month'])
            def delta(a,b):return (int(b[:4])-int(a[:4]))*12+int(b[5:])-int(a[5:])
            cohorts=defaultdict(set);active=defaultdict(set)
            for customer,month in first.items():cohorts[month].add(customer)
            for r in accepted:active[(first[r['customer']],delta(first[r['customer']],r['month']))].add(r['customer'])
            months=sorted(cohorts);horizon=data['observed_through'][:7];matrix=[]
            for month in months:
                for offset in range(delta(month,horizon)+1):
                    ids=sorted(active[(month,offset)]);matrix.append({'cohort':month,'month_index':offset,'denominator':len(cohorts[month]),'active_customers':len(ids),'customer_ids':ids,'retention':str(D(len(ids))/D(len(cohorts[month])))})
            check('取消首单不决定客户B所属cohort',first['B']=='2026-08');check('七月cohort分母始终两人',all(r['denominator']==2 for r in matrix if r['cohort']=='2026-07'));check('月份去重后同客户只算一次',next(r for r in matrix if r['cohort']=='2026-07' and r['month_index']==1)['active_customers']==1);cout('cohort-matrix.csv',matrix);result={'first_completed_month':first,'matrix':matrix}
