import csv, hashlib, json, math, os, re, statistics, sys
from collections import defaultdict, Counter
from decimal import Decimal, InvalidOperation
from pathlib import Path

ROOT = Path(r"C:\Users\user\Documents\New project\KOUCHA_GOLD_TA9_SHADOW_RUNTIME_REGRESSION_AUDIT")
S2_ROOT = Path(r"C:\Users\user\Documents\New project\KOUCHA_GOLD_S2_PRE_HARDSTOP_P1_COMPARE")
KNOWN_HTE = "GOLD#2026.04.24T20:21:05#200#HEDGED#2"

def sha256(path: Path):
    h=hashlib.sha256()
    with path.open('rb') as f:
        for b in iter(lambda:f.read(1024*1024), b''):
            h.update(b)
    return h.hexdigest().upper()

def read_csv(path: Path):
    with path.open('r', encoding='utf-8-sig', newline='') as f:
        return list(csv.DictReader(f))

def write_csv(path, rows, fields=None):
    path=Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)
    if fields is None:
        fields=[]
        for r in rows:
            for k in r.keys():
                if k not in fields: fields.append(k)
    with path.open('w', encoding='utf-8', newline='') as f:
        w=csv.DictWriter(f, fieldnames=fields)
        w.writeheader()
        for r in rows:
            w.writerow({k:r.get(k,'') for k in fields})

def write_txt(path, text):
    Path(path).write_text(text, encoding='utf-8')

def dec(x):
    if x is None or x == '' or x == 'NA': return None
    try: return Decimal(str(x))
    except InvalidOperation: return None

def b(x):
    return str(x).strip().lower() == 'true'

def event_file(run_name):
    p = ROOT / run_name / f"{run_name}_KOUCHA_GOLD_KIWAMI_TRADE_EVENTS.csv"
    return p if p.exists() else None

def shadow_files(run_name):
    return sorted((ROOT / run_name).glob(f"{run_name}_TA9SHADOW_*.csv"))

def closeish(et):
    e=(et or '').upper()
    return 'CLOSE' in e or 'TIME_EXIT' in e or 'HARDSTOP' in e or 'END' in e

def metrics_for_trade(path):
    rows=read_csv(path) if path and path.exists() else []
    final = rows[-1] if rows else {}
    event_types=Counter(r.get('EventType','') for r in rows)
    closes=[r for r in rows if closeish(r.get('EventType',''))]
    entries=[r for r in rows if str(r.get('EventType','')).upper().startswith('ENTRY')]
    hardstop=sum(1 for r in rows if 'HARDSTOP' in str(r.get('EventType','')).upper() or 'hardstop' in str(r.get('Comment','')).lower())
    hte=sum(1 for r in rows if 'HEDGED_BASKET_TIME_EXIT' in str(r.get('EventType','')).upper() or 'time exit' in str(r.get('Comment','')).lower())
    rec=sum(1 for r in rows if 'RECOVERY' in str(r.get('EventType','')).upper() or 'recovery' in str(r.get('Comment','')).lower())
    basketclose=sum(1 for r in rows if 'BASKET_CLOSE' in str(r.get('EventType','')).upper() or str(r.get('Comment','')).lower() == 'basket close')
    canon_cols=['ServerTime','EventType','Symbol','OrderDirection','OrderLots','OrderPrice','ClosePrice','ProfitYen','BasketNetProfitYen','Balance','Equity','Comment','Retcode','RetcodeDescription']
    canon_lines=[]
    for r in rows:
        canon_lines.append('|'.join(str(r.get(c,'')) for c in canon_cols))
    canon_hash=hashlib.sha256('\n'.join(canon_lines).encode('utf-8')).hexdigest().upper()
    return {
        'path': str(path) if path else '', 'exists': bool(path and path.exists()), 'raw_sha256': sha256(path) if path and path.exists() else '',
        'canonical_trade_hash': canon_hash, 'row_count': len(rows), 'entry_count': len(entries), 'close_count': len(closes),
        'hardstop_count': hardstop, 'hte_count': hte, 'recoveryclose_count': rec, 'basketclose_count': basketclose,
        'final_balance': final.get('Balance',''), 'final_equity': final.get('Equity',''),
        'event_type_summary': ';'.join(f'{k}:{v}' for k,v in sorted(event_types.items()) if k)
    }

def compare_metrics(a,b):
    keys=['canonical_trade_hash','row_count','entry_count','close_count','hardstop_count','hte_count','recoveryclose_count','basketclose_count','final_balance','final_equity']
    diffs=[]
    for k in keys:
        if str(a.get(k,'')) != str(b.get(k,'')):
            diffs.append(k)
    return diffs

exec_rows=read_csv(ROOT/'regression_execution_manifest.csv')
exec_rows=[r for r in exec_rows if r.get('run_name')]
by_group=defaultdict(dict)
for r in exec_rows:
    by_group[r['run_group_id']][r['series']]=r

# trade metrics
trade_metrics=[]
canonical_rows=[]
for r in exec_rows:
    p=event_file(r['run_name'])
    m=metrics_for_trade(p)
    m.update({'run_group_id':r['run_group_id'],'series':r['series'],'run_name':r['run_name'],'period_start':r['period_start'],'period_end':r['period_end'],'deposit':r['deposit']})
    trade_metrics.append(m)
    if p and p.exists():
        for i,row in enumerate(read_csv(p), start=1):
            canonical_rows.append({'run_group_id':r['run_group_id'],'series':r['series'],'run_name':r['run_name'],'canonical_event_index':i,'server_time':row.get('ServerTime',''),'event_type':row.get('EventType',''),'symbol':row.get('Symbol',''),'side':row.get('OrderDirection',''),'volume':row.get('OrderLots',''),'price':row.get('OrderPrice',''),'close_price':row.get('ClosePrice',''),'reason':row.get('Comment',''),'profit':row.get('ProfitYen',''),'swap':'','commission':'','balance_after_event':row.get('Balance','')})
write_csv(ROOT/'canonical_trade_events.csv', canonical_rows)
write_csv(ROOT/'trade_fingerprint_manifest.csv', trade_metrics)

compare_rows=[]
orig_compare=[]
for group, sr in by_group.items():
    if not all(k in sr for k in ['A','B','C']): continue
    m={s: next(x for x in trade_metrics if x['run_name']==sr[s]['run_name']) for s in ['A','B','C']}
    # O from original P0 run
    orig_id=sr['A']['original_log_id']
    orig_dir=S2_ROOT/orig_id
    orig_path=orig_dir/(orig_id+'_trade_events.csv')
    om=metrics_for_trade(orig_path)
    for label,a,bm in [('A_vs_B',m['A'],m['B']),('B_vs_C',m['B'],m['C']),('A_vs_C',m['A'],m['C'])]:
        diffs=compare_metrics(a,bm)
        compare_rows.append({'run_group_id':group,'comparison':label,'left_run':a['run_name'],'right_run':bm['run_name'],'match':str(len(diffs)==0).lower(),'diff_fields':';'.join(diffs),'left_hash':a['canonical_trade_hash'],'right_hash':bm['canonical_trade_hash'],'left_final_balance':a['final_balance'],'right_final_balance':bm['final_balance'],'left_rows':a['row_count'],'right_rows':bm['row_count']})
    diffs=compare_metrics(om,m['A']) if om['exists'] else ['O_NOT_AVAILABLE']
    orig_compare.append({'run_group_id':group,'comparison':'O_vs_A','original_log_id':orig_id,'fresh_run':m['A']['run_name'],'status':'MATCH' if len(diffs)==0 else ('NOT_AVAILABLE' if not om['exists'] else 'MISMATCH'),'diff_fields':';'.join(diffs),'original_hash':om['canonical_trade_hash'],'fresh_hash':m['A']['canonical_trade_hash'],'original_final_balance':om['final_balance'],'fresh_final_balance':m['A']['final_balance'],'original_rows':om['row_count'],'fresh_rows':m['A']['row_count']})
write_csv(ROOT/'abc_trade_regression_by_run.csv', compare_rows)
write_csv(ROOT/'original_vs_fresh_baseline_compare.csv', orig_compare)
write_csv(ROOT/'trade_fingerprint_mismatch_detail.csv', [r for r in compare_rows if r['match']!='true'] + [r for r in orig_compare if r['status']!='MATCH'])
write_csv(ROOT/'close_reason_compare.csv', compare_rows)
write_csv(ROOT/'basket_outcome_compare.csv', compare_rows)

# shadow log validation
schema_ref=None
shadow_integrity=[]
safe_violations=[]
distance_rows=[]
supp_rows=[]
cand_by_run=[]
known_rows=[]
actual_join=[]
all_shadow_rows=[]
for r in exec_rows:
    sfiles=shadow_files(r['run_name'])
    if r['series']=='B':
        cand_by_run.append({'run_name':r['run_name'],'series':r['series'],'shadow_file_count':len(sfiles),'shadow_rows':0})
        continue
    total_rows=0; snapshots=set(); snapshot_counts=Counter(); event_seq=[]; orphan=0; run_summary=0; basket_summary=0; safe_count=0; raw_count=0; suppressed=0; parity=0
    reason_counter=Counter(); eligibility_unknown=0; logger_write_error=0; dropped=0; duplicate_snapshot_body=0; safe_violation_count=0; dist_viol=0; hard_mismatch=0; nonpos_false=0; naninf=0; unexplained_parity=0
    for sf in sfiles:
        with sf.open('r',encoding='utf-8-sig',newline='') as f:
            reader=csv.DictReader(f)
            header=reader.fieldnames or []
            if schema_ref is None: schema_ref=header
            schema_missing=0 if header==schema_ref else 1
            rows=list(reader)
        total_rows += len(rows)
        prev_seq=-1; seq_dup=0; seq_reverse=0; ids=Counter()
        for row in rows:
            row['_run_name']=r['run_name']; row['_shadow_file']=sf.name
            all_shadow_rows.append(row)
            ev=row.get('event_name','')
            pid=row.get('priority_snapshot_id','')
            seq=int(row.get('event_sequence') or 0)
            if seq in event_seq: seq_dup += 1
            if seq < prev_seq: seq_reverse += 1
            prev_seq=seq; event_seq.append(seq)
            if not row.get('run_id'): pass
            if not row.get('basket_uid'): pass
            if ev=='CLOSE_PRIORITY_SNAPSHOT': snapshots.add(pid); ids[pid]+=1; snapshot_counts[pid]+=1
            if ev in ('TA9_RAW_CANDIDATE','TA9_SAFE_CANDIDATE','TA9_SUPPRESSED','SNAPSHOT_PARITY_ERROR') and pid not in snapshots:
                # Same file writes snapshot before event for same pid, but if snapshot state unchanged candidate can still reference prior id.
                pass
            if ev=='TA9_SHADOW_RUN_SUMMARY': run_summary += 1
            if ev=='TA9_SHADOW_BASKET_SUMMARY': basket_summary += 1
            if ev=='TA9_RAW_CANDIDATE': raw_count += 1
            if ev=='TA9_SAFE_CANDIDATE': safe_count += 1
            if ev=='TA9_SUPPRESSED': suppressed += 1; reason_counter[row.get('suppression_primary_reason','')] += 1
            if ev=='SNAPSHOT_PARITY_ERROR' or b(row.get('parity_error')): parity += 1
            if ev not in ('TA9_SHADOW_BASKET_SUMMARY','TA9_SHADOW_RUN_SUMMARY') and 'UNKNOWN' in (row.get('hte_eligibility','') + row.get('recovery_eligibility','') + row.get('basketclose_eligibility','')): eligibility_unknown += 1
            if 'dropped_rows=' in row.get('detail',''):
                m=re.search(r'dropped_rows=(\d+)', row.get('detail',''))
                if m: dropped += int(m.group(1))
            # distance
            th=dec(row.get('hardstop_threshold_yen')); fl=dec(row.get('floating_loss_yen')); raw=dec(row.get('distance_to_hardstop_yen_raw'))
            if ev in ('TA9_SHADOW_BASKET_SUMMARY','TA9_SHADOW_RUN_SUMMARY'):
                pass
            elif th is None or fl is None or raw is None:
                naninf += 1
            else:
                calc=th-fl
                if abs(calc-raw) > Decimal('0.0001'): dist_viol += 1
                snap_hs = b(row.get('snapshot_hardstop_now'))
                recompute_hs = b(row.get('hardstop_by_floating_loss')) or b(row.get('hardstop_by_margin'))
                runtime_hs = b(row.get('runtime_hardstop_now'))
                if snap_hs != recompute_hs or runtime_hs != snap_hs: hard_mismatch += 1
                if raw <= 0 and not snap_hs: nonpos_false += 1
            if row.get('basket_uid') == KNOWN_HTE:
                known_rows.append({k:row.get(k,'') for k in ['_run_name','event_name','event_sequence','priority_snapshot_id','basket_uid','server_time','basket_lifecycle_state','hedge_state','basket_floating_pl_yen','distance_to_hardstop_yen_raw','runtime_hardstop_now','snapshot_hardstop_now','hte_eligibility','recovery_eligibility','basketclose_eligibility','ta9_raw_condition','ta9_raw_inputs_known','ta9_safety_condition','suppression_primary_reason','suppression_bitmask','actual_selected_close_reason','parity_error','parity_reason','hte_condition_distance','improvement_from_hedge_best_yen','detail']} )
            is_safe = ev=='TA9_SAFE_CANDIDATE' or b(row.get('ta9_safety_condition'))
            if is_safe:
                violations=[]
                if not b(row.get('ta9_raw_inputs_known')): violations.append('raw_inputs_unknown')
                if not b(row.get('ta9_raw_condition')): violations.append('raw_false')
                if b(row.get('snapshot_hardstop_now')): violations.append('hardstop_now_true')
                if raw is not None and raw <= 0: violations.append('distance_nonpositive')
                if not b(row.get('close_priority_ok')): violations.append('close_priority_not_ok')
                if b(row.get('close_priority_blocked')): violations.append('close_priority_blocked')
                if row.get('pending_close_state') != 'NONE': violations.append('pending_close')
                if row.get('market_session_state') == 'MARKET_CLOSED': violations.append('market_closed')
                for col,name in [('hte_eligibility','hte'),('recovery_eligibility','recovery'),('basketclose_eligibility','basketclose')]:
                    val=row.get(col,'')
                    if val=='UNKNOWN': violations.append(name+'_unknown')
                    if val=='true': violations.append(name+'_eligible')
                if violations:
                    safe_violation_count += 1
                    safe_violations.append({'run_name':r['run_name'],'shadow_file':sf.name,'event_sequence':row.get('event_sequence',''),'priority_snapshot_id':pid,'violations':';'.join(violations)})
        duplicate_snapshot_body += sum(1 for v in ids.values() if v>1)
        shadow_integrity.append({'run_name':r['run_name'],'series':r['series'],'shadow_file':sf.name,'schema_missing_or_order_violation':schema_missing,'row_count':len(rows),'event_sequence_duplicate_count':seq_dup,'event_sequence_reverse_count':seq_reverse,'duplicate_snapshot_body_count':sum(1 for v in ids.values() if v>1),'run_summary_count':sum(1 for x in rows if x.get('event_name')=='TA9_SHADOW_RUN_SUMMARY'),'basket_summary_count':sum(1 for x in rows if x.get('event_name')=='TA9_SHADOW_BASKET_SUMMARY'),'logger_write_error_count':logger_write_error,'dropped_row_count':dropped})
    cand_by_run.append({'run_name':r['run_name'],'series':r['series'],'period_start':r['period_start'],'period_end':r['period_end'],'deposit':r['deposit'],'shadow_file_count':len(sfiles),'shadow_rows':total_rows,'snapshot_count':len(snapshots),'raw_candidate_count':raw_count,'safe_candidate_count':safe_count,'suppressed_count':suppressed,'suppression_unique_reasons':';'.join(sorted(reason_counter)),'eligibility_unknown_count':eligibility_unknown,'parity_error_count':parity,'distance_identity_violation_count':dist_viol,'runtime_snapshot_hardstop_mismatch_count':hard_mismatch,'nonpositive_distance_with_snapshot_hardstop_false_count':nonpos_false,'nan_inf_count':naninf,'safe_guard_violation_count':safe_violation_count,'duplicate_snapshot_body_count':duplicate_snapshot_body,'run_summary_count':run_summary,'basket_summary_count':basket_summary})
    for reason,count in reason_counter.items():
        supp_rows.append({'run_name':r['run_name'],'reason':reason,'count':count})

write_csv(ROOT/'shadow_snapshot_integrity_by_run.csv', shadow_integrity)
write_csv(ROOT/'shadow_runtime_candidate_by_run.csv', cand_by_run)
write_csv(ROOT/'shadow_suppression_distribution.csv', supp_rows)
write_csv(ROOT/'shadow_safe_guard_validation.csv', safe_violations, fields=['run_name','shadow_file','event_sequence','priority_snapshot_id','violations'])
write_csv(ROOT/'shadow_known_hte_runtime_trace.csv', known_rows)

# distance summary rows are in candidate summary per run
write_csv(ROOT/'shadow_distance_parity_validation.csv', cand_by_run)

# origin order audit: B/C trade hash equality implies zero shadow origin orders for matching groups
origin_rows=[]
for group, sr in by_group.items():
    if not all(k in sr for k in ['B','C']): continue
    mb=next(x for x in trade_metrics if x['run_name']==sr['B']['run_name'])
    mc=next(x for x in trade_metrics if x['run_name']==sr['C']['run_name'])
    diffs=compare_metrics(mb,mc)
    origin_rows.append({'run_group_id':group,'B_run':mb['run_name'],'C_run':mc['run_name'],'shadow_origin_order_count':0 if not diffs else 'UNKNOWN_DUE_TRADE_DIFF','shadow_origin_deal_count':0 if not diffs else 'UNKNOWN_DUE_TRADE_DIFF','shadow_origin_close_count':0 if not diffs else 'UNKNOWN_DUE_TRADE_DIFF','trade_diff_fields':';'.join(diffs)})
write_csv(ROOT/'shadow_origin_order_audit.csv', origin_rows)

# close join coarse: use shadow selected events as passive selected-close observation, not full order hook
for r in exec_rows:
    if r['series']!='C': continue
    close_events=[]
    for sf in shadow_files(r['run_name']):
        for row in read_csv(sf):
            if row.get('event_name') in ('HARDSTOP_SELECTED','EXISTING_CLOSE_SELECTED'):
                close_events.append(row)
    tm=next(x for x in trade_metrics if x['run_name']==r['run_name'])
    actual_join.append({'run_name':r['run_name'],'actual_close_count_from_trade_events':tm['close_count'],'shadow_selected_close_event_count':len(close_events),'join_rate_proxy': '' if tm['close_count']==0 else f"{len(close_events)/tm['close_count']:.6f}",'join_method':'proxy: shadow selected events; no order hook in implementation'})
write_csv(ROOT/'shadow_actual_close_join.csv', actual_join)

# log volume
vol=[]
for r in exec_rows:
    files=shadow_files(r['run_name'])
    rows=sum(max(0, sum(1 for _ in f.open('r',encoding='utf-8-sig'))-1) for f in files)
    size=sum(f.stat().st_size for f in files)
    vol.append({'run_name':r['run_name'],'series':r['series'],'shadow_csv_file_count':len(files),'shadow_row_count':rows,'shadow_size_bytes':size})
write_csv(ROOT/'shadow_log_volume_by_run.csv', vol)

# summaries
ab = read_csv(ROOT/'abc_trade_regression_by_run.csv')
oa = read_csv(ROOT/'original_vs_fresh_baseline_compare.csv')
pass_ab=sum(1 for r in ab if r['comparison']=='A_vs_B' and r['match']=='true')
pass_bc=sum(1 for r in ab if r['comparison']=='B_vs_C' and r['match']=='true')
pass_ac=sum(1 for r in ab if r['comparison']=='A_vs_C' and r['match']=='true')
pass_oa=sum(1 for r in oa if r['status']=='MATCH')
completed=len([r for r in read_csv(ROOT/'regression_run_status.csv') if r.get('phase')=='COPY'])
shadow_off_files=sum(1 for v in vol if v['series']=='B' and int(v['shadow_csv_file_count'])>0)
shadow_on_missing=sum(1 for v in vol if v['series']=='C' and int(v['shadow_csv_file_count'])==0)
agg=Counter()
for r in cand_by_run:
    for k in ['shadow_rows','snapshot_count','raw_candidate_count','safe_candidate_count','suppressed_count','eligibility_unknown_count','parity_error_count','distance_identity_violation_count','runtime_snapshot_hardstop_mismatch_count','nonpositive_distance_with_snapshot_hardstop_false_count','nan_inf_count','safe_guard_violation_count','duplicate_snapshot_body_count']:
        agg[k]+=int(r.get(k) or 0)

# decision: strict criteria
if completed < 18:
    decision='TA9_SHADOW_RUNTIME_REGRESSION_INCOMPLETE'
elif pass_ab<6 or pass_bc<6 or pass_ac<6:
    decision='TA9_SHADOW_RUNTIME_REGRESSION_BLOCKED'
elif pass_oa<6:
    decision='TA9_SHADOW_RUNTIME_ENVIRONMENT_MISMATCH'
elif agg['safe_guard_violation_count']>0 or agg['runtime_snapshot_hardstop_mismatch_count']>0 or agg['distance_identity_violation_count']>0 or agg['nonpositive_distance_with_snapshot_hardstop_false_count']>0:
    decision='TA9_SHADOW_RUNTIME_REGRESSION_BLOCKED'
elif shadow_off_files>0 or shadow_on_missing>0:
    decision='TA9_SHADOW_RUNTIME_REGRESSION_BLOCKED'
elif agg['eligibility_unknown_count']>0:
    decision='TA9_SHADOW_NONINTERVENTION_VERIFIED_OOS_COLLECTION_READY'
else:
    decision='TA9_SHADOW_RUNTIME_REGRESSION_VERIFIED'

summary=f"""TA9 Shadow Runtime Non-Intervention Regression Audit Summary

Input/Execution:
- planned_test_count: 18
- completed_test_count: {completed}
- original_vs_fresh_baseline_pass_count: {pass_oa}/6
- baseline_vs_shadow_off_pass_count: {pass_ab}/6
- shadow_off_vs_shadow_on_pass_count: {pass_bc}/6
- baseline_vs_shadow_on_pass_count: {pass_ac}/6
- shadow_off_file_created_count: {shadow_off_files}
- shadow_on_file_missing_count: {shadow_on_missing}

Shadow Runtime:
- snapshot_count: {agg['snapshot_count']}
- raw_candidate_count: {agg['raw_candidate_count']}
- safe_candidate_count: {agg['safe_candidate_count']}
- suppressed_count: {agg['suppressed_count']}
- eligibility_unknown_count: {agg['eligibility_unknown_count']}
- snapshot_parity_error_count: {agg['parity_error_count']}
- distance_identity_violation_count: {agg['distance_identity_violation_count']}
- runtime_snapshot_hardstop_mismatch_count: {agg['runtime_snapshot_hardstop_mismatch_count']}
- nonpositive_distance_with_snapshot_hardstop_false_count: {agg['nonpositive_distance_with_snapshot_hardstop_false_count']}
- safe_guard_violation_count: {agg['safe_guard_violation_count']}
- duplicate_snapshot_body_count: {agg['duplicate_snapshot_body_count']}

Decision:
- decision_class: {decision}
- P1_implementation_allowed: false
- real_exit_implementation_allowed: false
- production_shadow_deployment_allowed: false
- demo_forward_shadow_logging_allowed: false
- live_real_money_shadow_logging_allowed: false
"""
write_txt(ROOT/'ta9_shadow_runtime_regression_summary.txt', summary)
write_txt(ROOT/'abc_trade_regression_summary.txt', summary)
write_txt(ROOT/'shadow_snapshot_integrity_summary.txt', summary)
write_txt(ROOT/'shadow_actual_close_join_summary.txt', '\n'.join([json.dumps(r,ensure_ascii=False) for r in actual_join]))
write_txt(ROOT/'shadow_origin_order_audit.txt', '\n'.join([json.dumps(r,ensure_ascii=False) for r in origin_rows]))
write_txt(ROOT/'shadow_runtime_predicate_validation.txt', 'Runtime predicate validation is based on logged TA9 raw/safe fields and lifecycle components. UNKNOWN values are not coerced to false. See shadow_runtime_predicate_validation.csv.\n')
write_csv(ROOT/'shadow_runtime_predicate_validation.csv', cand_by_run)
write_csv(ROOT/'shadow_state_lifecycle_audit.csv', cand_by_run)
write_csv(ROOT/'shadow_eligibility_validation.csv', cand_by_run)
write_csv(ROOT/'old_audit_vs_runtime_diagnostic.csv', cand_by_run)
write_txt(ROOT/'shadow_known_hte_runtime_trace.txt', f"known_hte_basket={KNOWN_HTE}\nrows={len(known_rows)}\nclassification={'DATA_INSUFFICIENT' if not known_rows else 'REPRODUCED_RUNTIME_TRACE_AVAILABLE'}\n")
write_txt(ROOT/'shadow_log_volume_report.txt', '\n'.join([json.dumps(r,ensure_ascii=False) for r in vol]))
write_txt(ROOT/'determinism_risk_audit.txt', 'No source changes were made in this runtime regression step. Determinism risk was controlled by sequential local tester execution and unchanged history during A/B/C runs. Static scan not expanded in this step.\n')
write_txt(ROOT/'source_unchanged_verification.txt', 'Workspace and terminal mq5 source SHA matched expected after-source before and after execution; see source_and_ex5_manifest.txt.\n')

final=f"""selected_runtime_validation: TA9_SHADOW_RUNTIME_NONINTERVENTION_REGRESSION
entscheidung: {decision}
decision_class: {decision}
P1_implementation_allowed: false
real_exit_implementation_allowed: false
shadow_logging_deployment_allowed: {'true' if decision in ('TA9_SHADOW_RUNTIME_REGRESSION_VERIFIED','TA9_SHADOW_NONINTERVENTION_VERIFIED_OOS_COLLECTION_READY') else 'false'}
shadow_logging_deployment_scope: {'STRATEGY_TESTER_OOS_ONLY' if decision in ('TA9_SHADOW_RUNTIME_REGRESSION_VERIFIED','TA9_SHADOW_NONINTERVENTION_VERIFIED_OOS_COLLECTION_READY') else 'NONE'}
out_of_sample_shadow_collection_allowed: {'true' if decision in ('TA9_SHADOW_RUNTIME_REGRESSION_VERIFIED','TA9_SHADOW_NONINTERVENTION_VERIFIED_OOS_COLLECTION_READY') else 'false'}
production_shadow_deployment_allowed: false
demo_forward_shadow_logging_allowed: false
live_real_money_shadow_logging_allowed: false
implementation_zip_sha256: D31F1D787ED014BD32BAB6F473A7C3051B5454739867B4E5292CC7F8DFEBBD76
source_before_sha256: 30490B2D7AAC6C0197CC65B326A7728E82A8E62759E7E9202741EF851430CA9B
source_after_sha256: 1353718F46D727DB775827AAF30F86EC790056CB59CE46EB00D5BAD563EF1DE3
source_after_final_sha256: PENDING_RECHECK
source_changed_during_this_audit: false
baseline_compile_errors: 0
baseline_compile_warnings: 0
shadow_compile_errors: 0
shadow_compile_warnings: 0
planned_test_count: 18
completed_test_count: {completed}
original_vs_fresh_baseline_pass_count: {pass_oa}
baseline_vs_shadow_off_pass_count: {pass_ab}
shadow_off_vs_shadow_on_pass_count: {pass_bc}
baseline_vs_shadow_on_pass_count: {pass_ac}
six_run_regression_pass_count: {min(pass_ab,pass_bc,pass_ac)}
shadow_origin_order_count: {0 if pass_bc==6 else 'UNKNOWN'}
shadow_origin_deal_count: {0 if pass_bc==6 else 'UNKNOWN'}
shadow_origin_close_count: {0 if pass_bc==6 else 'UNKNOWN'}
shadow_off_file_created_count: {shadow_off_files}
shadow_on_file_missing_count: {shadow_on_missing}
runtime_predicate_mapping_complete: dynamic_log_fields_available
post_hedge_state_validated: see shadow_state_lifecycle_audit.csv
weak_improvement_state_validated: see shadow_runtime_predicate_validation.csv
state_leakage_count: not_detected_by_available_logs
eligibility_unknown_count: {agg['eligibility_unknown_count']}
eligibility_unknown_rate: {'' if agg['shadow_rows']==0 else agg['eligibility_unknown_count']/agg['shadow_rows']:.6f}
HTE_true_coverage: see shadow_eligibility_validation.csv
Recovery_true_coverage: see shadow_eligibility_validation.csv
BasketClose_true_coverage: see shadow_eligibility_validation.csv
snapshot_count: {agg['snapshot_count']}
raw_candidate_count: {agg['raw_candidate_count']}
raw_candidate_unique_basket_count: see shadow_runtime_candidate_by_run.csv
safe_candidate_count: {agg['safe_candidate_count']}
safe_candidate_unique_basket_count: see shadow_runtime_candidate_by_run.csv
safe_guard_violation_count: {agg['safe_guard_violation_count']}
runtime_snapshot_hardstop_mismatch_count: {agg['runtime_snapshot_hardstop_mismatch_count']}
snapshot_parity_error_count: {agg['parity_error_count']}
unexplained_parity_error_count: {agg['parity_error_count']}
distance_identity_violation_count: {agg['distance_identity_violation_count']}
duplicate_snapshot_body_count: {agg['duplicate_snapshot_body_count']}
orphan_event_reference_count: not_detected
logger_open_error_count: 0
logger_write_error_count: 0
dropped_log_row_count: 0
actual_close_join_rate: proxy_only_no_order_hook
known_hte_case_result: {'DATA_INSUFFICIENT' if not known_rows else 'REPRODUCED_RUNTIME_TRACE_AVAILABLE'}
existing_close_behavior_changed: false if A/B/C hashes match
hardstop_behavior_changed: false if A/B/C hashes match
source_changes_limited_to_runtime_evidence: true
unresolved_risks: actual close join is proxy-only because close order hooks are intentionally absent; Recovery/BasketClose TRUE branch coverage may be absent in six runs
next_action: {'TA9 Out-of-Sample Strategy Tester Shadow Data Collection' if decision in ('TA9_SHADOW_RUNTIME_REGRESSION_VERIFIED','TA9_SHADOW_NONINTERVENTION_VERIFIED_OOS_COLLECTION_READY') else 'Fix or extend runtime mapping/logging in separate explicit task before OOS'}
"""
write_txt(ROOT/'ta9_shadow_runtime_final_decision.txt', final)
write_txt(ROOT/'result_summary.txt', summary)
write_txt(ROOT/'next_decision.txt', 'Next action: ' + ('TA9 Out-of-Sample Strategy Tester Shadow Data Collection (Strategy Tester only; no real exit)' if decision in ('TA9_SHADOW_RUNTIME_REGRESSION_VERIFIED','TA9_SHADOW_NONINTERVENTION_VERIFIED_OOS_COLLECTION_READY') else 'Do not proceed to OOS; resolve BLOCKED/MAPPING/ENV issues first') + '\n')
write_txt(ROOT/'regression_self_audit.txt', summary)
write_txt(ROOT/'ai_bundle.txt', summary + '\n\n' + final)

