"""Generate bounded Markdown tables from the JSON-only analysis."""
import datetime
import hashlib
import json
import pathlib
import subprocess
import sys
import analyze

RUN=pathlib.Path(__file__).resolve().parent.parent
TOPIC=pathlib.Path('/workspace/contextra-analytics-runtime-lifecycle-refactor/docs/profiles/ltb1021/topics/runtime-lifecycle')


def num(v):return '없음' if v is None else f'{v:.6g}'
def percent(v):return '없음' if v is None else f'{v*100:.2f}%'
def ratio(n,total):return f'{n}/{total} ({n/total*100:.2f}%)' if total else '0/0 (없음)'


def interval(m):return f"[{num(m['lower'])}, {num(m['upper'])}{']' if m['upper_closed'] else ')'}"


def table(lines,headers,rows):
    lines.append('| '+' | '.join(headers)+' |');lines.append('| '+' | '.join('---' for _ in headers)+' |')
    for row in rows:lines.append('| '+' | '.join(str(x) for x in row)+' |')
    lines.append('')


def make_report(result):
    lines=['# PoseC3D 주 신호 점수 분포와 threshold 후보','',
           f"- 작성: {result['timestamp']} / profile `ltb1021`, test.",
           '- 상태: `JSON_STATISTICS_VERIFIED / USER_REVIEW_WAIT`. 재추론·영상 분석·운영 적용 없음.',
           f'- Owner: [Runtime Lifecycle]({TOPIC}/README.ko.md).',
           '- 범위: 기존 짧은 입력16관측의 camera24+추가6개, NTU60의0-based23/49/50/51. 구간 전체 분류이며 객체별 결과가 아니다.',
           '- 사용자 설명: 추가6개는 등장인물이 싸움 대상이고 대부분 구간이 싸움이다. 정확한 시작/끝·클래스별 정답은 없다.',
           '- 해석: 통과율은 유효 관측 중 조건 충족 비율이며 검출률·정확도·재현율이 아니다. 정상 비교자료가 없어 오탐 억제 효과도 미확정이다.',
           '- 반복 영상과 겹치는 입력창은 독립적인 검증 사례가 아니다. 네 행동이 모든 싸움 구간에서 동시에 발생한다고 가정하지 않는다.','',
           '## 재현 근거','',f"- 입력 root: `{result['source']}`.",f'- 분석 run: `{RUN}`.',
           f"- PoseC3D checkpoint SHA256: `{result['weight']['sha256']}`.",
           f"- 기존 SHA manifest와 JSON/클래스CSV/summary/manifest {len(result['fingerprints'])}파일 지문 대조 PASS. 미디어 파일은 열거나 hash하지 않았다.",
           '- 전체60개 score 길이/유한값/[0,1]/softmax 합 오차<1e-5, input_shape 및 입력창16관측, metadata sample·Frame·PTS 순서를 검사했다.',
           '- 점수가 없는 관측은 제외 사유와 분모를 보존하고0으로 채우지 않았다. 점수 합산·재정규화·추가 관절 필터는 없다.',
           '- 정확한 지문/숫자/구간 민감도는 `statistics.json`, 표적 테스트는 `validation.json`, 재현 코드는 `helpers/`에 보존한다.',
           '- 표는 유효숫자6자리/백분율2자리로 표시한다. 예: `2.3e-6 = 0.0000023`. 계산과 cutoff 적용은 반올림 전 값을 사용한다.','',
           '## 유효 관측','']
    rows=[]
    for clip,v in result['validity'].items():
        rows.append([clip,v['total'],v['valid'],percent(v['valid_ratio']),'; '.join(f'{k}: {n}' for k,n in v['excluded'].items()) or '없음'])
    table(lines,['입력','전체','유효','유효 비율','제외 사유'],rows)
    total=result['actual']['total'];valid=result['actual']['valid']
    lines += [f'전체: {valid}/{total} ({valid/total*100:.2f}%) 유효. 제외385개=입력 구간 부족90개+유효 skeleton 없음295개.',
              'camera24는 기존201표시 관측(원본242~723 안의 실제표본242~722), 나머지는 기존 전체 표시 구간이다.','',
              '## 계산법','',
              '- Quantile: R7/linear, 정렬값의 h=(N-1)p 위치를 선형 보간한다. stdlib statistics.quantiles(method=inclusive)와 교차 확인했다.',
              '- 전체(ALL)는4316유효값을 직접 합친 통계다. 긴 영상·유효 관측이 많은 영상의 영향이 크다. 영상별 중앙값/P95를 평균하지 않았다.',
              '- 영상 동일가중 평균은 각 영상 평균7개의 산술평균이며 별도 보조값이다. 유효0영상이 있으면 제외·사용 영상 수를 명시한다.',
              '- Histogram: 클래스별 ALL의 min~max 공통 범위, 동일폭 bin. 폭=2*IQR/N^(1/3), 개수=ceil(range/폭)의 Freedman–Diaconis 규칙이다.',
              '- 구간 수 상한65536(이번에는 미발동). IQR0은 Sturges 대체, 모든 값 동일은 닫힌 단일구간이다. 같은 클래스의7영상/ALL에 같은 경계를 쓴다.',
              '- 구간은 [lower,upper), 마지막 구간만 upper 포함. 최빈구간 동률이면 모두 보존한다. 구간 내 평균은 Mode 자체와 다른 보조값이다.',
              '- 구간 수를 기본/절반/두 배로 바꾼 제한적 민감도 확인. 단일 최빈구간들이 겹치면 `검사한3폭의 위치구간 겹침`, 동률/비겹침은 별도 표시한다.',
              '- 위치구간 겹침은 정확한 Mode 값·구간 내 평균·비율이 불변이라는 보장이 아니다. P95는 최빈구간을 대체하지 않는다.',
              '- 최고값 첫 위치는 입력명 순서→원본Frame 순서로 선택하며 정확한 float 동률 개수도 남긴다.',
              '- Top5는 전체60클래스를 (-score, 0-based ID)로 정렬한다. 다른56개 분포를 분석하거나 score를 합치지는 않았다.','',
              '## 전체 요약','']
    table(lines,['ID/클래스','N','평균','P50','P95','최댓값','영상 동일가중 평균','Top5 포함'],[
        [f"{key} {c['name']}",c['groups']['ALL']['stats']['n'],num(c['groups']['ALL']['stats']['mean']),num(c['groups']['ALL']['stats']['p50']),
         num(c['groups']['ALL']['stats']['p95']),num(c['groups']['ALL']['stats']['max']),num(c['equal_video_mean']),ratio(c['groups']['ALL']['top5_count'],valid)]
        for key,c in result['classes'].items()])
    flags={'PEAK_INTERVALS_OVERLAP':'검사한3폭의 위치구간 겹침','MULTIPLE_MAX_BINS':'최빈구간 동률: 단일 위치 미확정',
           'BIN_WIDTH_SENSITIVE':'구간 폭에 민감','NO_VALID_VALUES':'유효값 없음'}
    for key,c in result['classes'].items():
        lines += [f"## {key} · {c['name']}",'','### 기본 통계','']
        table(lines,['입력','N','최소','평균','P25','P50','P75','P95','최대'],[
            [clip,g['stats']['n']]+[num(g['stats'][k]) for k in ('min','mean','p25','p50','p75','p95','max')] for clip,g in c['groups'].items()])
        lines += ['### 최고값 위치','']
        table(lines,['입력','첫 최고값의 원본 입력/Frame','PTS초','동률 수'],[
            [clip,f"{g['max_first']['clip']} / {g['max_first']['source_frame']}" if g['max_first'] else '없음',
             num(g['max_first']['pts']) if g['max_first'] else '없음',g['max_ties']] for clip,g in c['groups'].items()])
        spec=c['histogram_spec'];lines += ['### 최빈구간','',f"공통 구간 수{spec['bins']}, 폭{num((spec['hi']-spec['lo'])/spec['bins'])}, cap 적용={spec['cap_applied']}.",'']
        modal_rows=[]
        for clip,g in c['groups'].items():
            for m in g['modal']['base']['modes']:
                modal_rows.append([clip,interval(m),num(m['within_bin_mean']),ratio(m['count'],g['stats']['n']),flags[g['modal']['flag']]])
        table(lines,['입력','최빈구간','구간 내 평균','관측 수/유효 N','구간폭 민감도'],modal_rows)
        lines += ['ALL의 구간폭 민감도 상세:','']
        table(lines,['구간 수','폭','최빈구간','구간 내 평균','비율'],[
            [v['bins'],num(v['width']),'; '.join(interval(m) for m in v['modes']),'; '.join(num(m['within_bin_mean']) for m in v['modes']),
             '; '.join(percent(m['share']) for m in v['modes'])] for v in c['groups']['ALL']['modal']['sensitivity']])
        lines += ['### 공통 threshold별 통과','',
                  '분모는 해당 입력의 유효 관측이다. Top5 결합 비율도 조건 통과 관측이 아닌 같은 유효 N을 분모로 한다.','']
        for t in c['thresholds']:
            lines += [f"#### {'/'.join(t['labels']).upper()} · cutoff={t['cutoff']:.17g}",'']
            table(lines,['입력','유효 N','score>=cutoff','score>=cutoff AND Top5'],[
                [clip,g['n'],ratio(g['pass_count'],g['n']),ratio(g['top5_pass_count'],g['n'])] for clip,g in t['groups'].items()])
        lines += ['### 두 묶음 경계 검토','',
                  '보수적 histogram screen: 국소 peak 높이>=max(5,최고 bin수의15%), peak 간격>=3bin, 중간 valley<=작은 peak의25%, valley 양쪽에 각각 전체10% 이상.',
                  '이는 기술적 탐색 규칙이지 행동 분류의 검증된 기준이 아니다. 구간 폭을 바꿔도 재현되지 않는 경계는 추천하지 않는다.',
                  '구간 수별 후보 수: '+', '.join(f"{v['bins']} bins → {len(v['candidates'])}개" for v in c['valley_scans'])+'.',
                  '이번 검사에서는 별도 valley cutoff를 추가하지 않는다. 이 결과가 모든 형태의 다봉성 부재를 증명하는 것은 아니다.','']
    lines += ['## 통계 기반 추천과 한계','',
              '1. 51번 pushing other person을 다음 수치 검토의 우선 후보로 둔다. 네 후보 중 전체 평균·중앙값·Top5 포함 비율이 가장 크다. 이를 실제 밀기나 싸움 정답으로 확정하지 않는다.',
              '2. 51번 P50(0.008533163461834192)을 중간 탐색 기준으로, P75(0.043792348355054855)를 더 선별적인 비교값으로 제시한다. 전체 통과는 각각50%/25%이며 percentile에서 도출한 비율이지 효과 입증이 아니다.',
              '3. 51번 P50 AND Top5는1589/4316(36.82%), P75 AND Top5는1038/4316(24.05%)다. Top5는 더 적은 후보를 남기는 비교안일 뿐 오탐 감소가 검증된 것이 아니다.',
              '4. 23/49/50은 P25/P50을 낮게 설정하면 많은 관측이 통과하지만 모델 전체60개 중 Top5에 드는 비율은 낮다. 특히 camera24에서49/50의 P25 단독은100% 통과하지만 Top5 결합은0%다. 단순히 threshold를 낮춰 통과율을 높이는 방식은 우선 추천하지 않는다.',
              '5. 클래스별로 동일 cutoff를7영상에 적용한 차이를 먼저 본다. 다른 클래스에 같은 절대 threshold를 강제하거나 영상마다 통과율을 맞추는 튜닝은 하지 않는다.',
              '6. 네 점수를 합친 싸움 score, 주 신호 N개 조건, 시간 누적·평활·운영 Event 규칙은 만들지 않았다. 정상 비교자료와 정확한 행동 구간이 없어 최적 운영값은 정할 수 없다.',
              '7. 최빈구간은 모두 낮은 쪽이지만 구간 내 평균/비율은 폭에 따라 바뀐다. 단일 최빈값이나 P95로 모든 구간을 대표시키지 않는다.','',
              '## 검증·보호·다음 단계','',
              '- 원시 지문23파일, schema/softmax/16관측, 분모/제외합계, R7 독립 교차검산, 공통 histogram 합계/경계, pooled 가중평균, threshold count와 bisect 독립 검산 PASS.',
              '- 단위9개: R7 표본·자료 없음·구간 경계/최대포함·최빈 동률·상수·FD/IQR0·threshold 동률·Top5 동률·valley 강제생성 방지.',
              '- test의 저장된 JSON/CSV만 CPU로 읽었다. AI 서버 접속·GPU·Runtime 조작·미디어 열람/디코딩·Pose/Tracking/모델 실행·새 의존성·CSV/Excel/HTML/영상 생성 없음.',
              '- AVA/YOWOv2 운영 적용 및 camera27 쓰러짐 시험은 계속 보류한다. 결과로 새 필터나 영상 생성을 자동 시작하지 않는다.',
              '- 사용자와 통계 해석·다음 비교 조건을 논의한다. 이번 문서/새 분석 run은 과거 evidence와 분리했으며 원시 결과는 보존한다.','']
    return '\n'.join(lines)


if __name__=='__main__':
    test=subprocess.run([sys.executable,str(RUN/'helpers/test_statistics.py'),'-v'],capture_output=True,text=True,timeout=30)
    assert test.returncode==0,test.stderr
    result=analyze.compute()
    (RUN/'statistics.json').write_text(json.dumps(result,ensure_ascii=False,indent=2))
    report=make_report(result);(RUN/'analysis_report.ko.md').write_text(report)
    verification=dict(status='PASS',unit_test_output=test.stdout+test.stderr,checks=result['validation'],
                      helper_sha256={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted((RUN/'helpers').glob('*.py'))},
                      statistics_sha256=hashlib.sha256((RUN/'statistics.json').read_bytes()).hexdigest(),
                      report_sha256=hashlib.sha256(report.encode()).hexdigest())
    (RUN/'validation.json').write_text(json.dumps(verification,ensure_ascii=False,indent=2))
    print(json.dumps(dict(status='PASS',timestamp=result['timestamp'],report=str(RUN/'analysis_report.ko.md'),bytes=len(report.encode()),
                         flags={k:{n:g['modal']['flag'] for n,g in c['groups'].items()} for k,c in result['classes'].items()}),ensure_ascii=False))
