            import itertools
            shifts=data['shifts'];rest=output['min_rest_hours']
            def solve(items):
                answers=[]
                for people in itertools.product(*(r['allowed'] for r in items)):
                    assignment=dict(zip((r['id'] for r in items),people));ok=True
                    for a,b in itertools.combinations(items,2):
                        if assignment[a['id']]!=assignment[b['id']]:continue
                        first,last=sorted([a,b],key=lambda r:r['start']);gap=(datetime.fromisoformat(last['start'])-datetime.fromisoformat(first['end'])).total_seconds()/3600
                        if gap<rest:ok=False;break
                    if ok:answers.append(assignment)
                return answers
            answers=solve(shifts);core=[]
            if not answers:
                for n in range(1,len(shifts)+1):
                    found=next((list(items) for items in itertools.combinations(shifts,n) if not solve(list(items))),None)
                    if found is not None:core=[r['id'] for r in found];break
            relaxed=copy.deepcopy(shifts);relaxed[-1]['allowed']=['A','B'];relaxed_answers=solve(relaxed)
            check('原约束全局不可行而不是漏分配',answers==[]);check('最小不可行核心包含三个班次',core==['N1','M1','E1']);check('移除核心任一班次都可行',all(solve([s for s in shifts if s['id']!=removed]) for removed in core));check('明确修改候选后才出现解',len(relaxed_answers)>0);jout('shift-infeasibility.json',{'status':'infeasible','min_rest_hours':rest,'minimal_core':core,'original_assignments':answers,'relaxed_counterexample':relaxed_answers});result={'status':'infeasible','minimal_core':core,'relaxed_examples':relaxed_answers}
