# -*- coding: utf-8 -*-
r"""
tree_forge.py — 증상 기반 판단트리 생성기 [CEO 설계 2026-07-19]

CEO 설계 사상 (원문 요지):
  현장은 교과서(문제→원인→대응) 순서로 안 움직인다. 장인은
  증상 → 의심 → 확인 → 판단 → 조치 순서로 간다. AI는 문서를 읽는 게 아니라
  판단트리를 먼저 타고, 판단이 끝나면 그때 패턴노트를 읽고, 마지막에 사례를 붙인다.
  질문 → 판단트리 → 패턴노트 → 사례검색 → 최종답변.

동작:
  패턴노트 8장 + 결함 사례를 재료로, 페이블이 증상군별 판단트리를 만든다.
  각 트리: "작업자가 보는/말하는 증상"에서 출발 → 예/아니오 질문으로 좁힘 →
  후보 원인을 가능성 높은 순으로 하나씩 제거 → 각 갈래 끝에 참조할 패턴노트 명시.
  저장: E:\VLA시스템\KNOWLEDGE\_판단트리\TREE-증상군.md (지식서버 자동 인덱스, TREE- 최우선 가중치)

실행: python tree_forge.py
"""
import os, io, glob, subprocess, time

PATTERN_DIR = r'E:\VLA시스템\KNOWLEDGE\_패턴씨앗'
OUT_DIR = r'E:\VLA시스템\KNOWLEDGE\_판단트리'
CLAUDE_EXE = os.path.expanduser(r'~\.local\bin\claude.exe')

# 작업자가 실제로 입에 올리는 증상군 (전문용어가 아니라 보이는 현상 기준)
SYMPTOM_GROUPS = [
    ('구멍-터짐',   '구멍이 났다 / 뚫렸다 / 쇳물이 흘러내렸다 / 터졌다', ['용락', '갭관리', '토치각도']),
    ('비드-이상',   '비드가 솟는다 / 퍼진다 / 파인다 / 지저분하다 / 그라인더를 많이 쓰게 된다', ['비드불량', '언더컷', '토치각도', '갭관리']),
    ('금-갈라짐',   '금이 갔다 / 갈라졌다 / 검사에서 결함이 나왔다', ['크랙', '기공']),
    ('치수-휨',     '치수가 안 맞다 / 휘었다 / 클램프 풀었더니 틀어졌다', ['열변형', '갭관리']),
    ('속-안붙음',   '속이 안 붙었다 / 떼보니 안 붙어 있다 / 용입이 안 됐다', ['갭관리', '비드불량', '토치각도']),
]

PROMPT = ('너는 용접·제관 30년 장인의 진단 사고를 판단트리로 옮기는 정제자다.\n'
          '작업자가 "%s" 라고 말하는 상황에서, 장인이 머릿속으로 타는 예/아니오 질문 트리를 만들어라.\n'
          '재료는 아래 패턴노트들이다. 패턴노트에 있는 사실·수치·사례만 사용하라(지어내기 금지).\n'
          '규칙:\n'
          '- 트리는 증상에서 출발한다. 원인 나열이 아니라, 가능성 높은 것부터 하나씩 제거하는 순서다.\n'
          '- 각 질문은 작업자가 그 자리에서 답할 수 있는 것만 (눈으로 보거나, 재거나, 설정값을 읽어서).\n'
          '- 질문마다 "왜 이걸 먼저 묻는지" 한 줄 이유를 붙여라.\n'
          '- 각 갈래의 끝에는: 의심 원인 → 바로 할 조치 → 참조할 패턴노트 이름을 적어라.\n'
          '- 수치를 쓸 땐 출처 제품을 병기하고 "다른 제품에 그대로 적용 금지" 안전장치를 붙여라.\n'
          '- 용접 모르는 사람도 읽게: 전문용어에 쉬운 말 병기.\n'
          '출력(마크다운):\n'
          '# %s — 현장 판단트리\n> (서문 2~3문장: 언제 이 트리를 타는지)\n'
          '## 트리\n(Q1부터 들여쓰기 텍스트 트리. 각 Q에 [이유] 한 줄)\n'
          '## 갈래별 결론표\n(의심원인 | 즉시 조치 | 참조 패턴노트)\n\n'
          '[패턴노트들]\n%s')


def fable(prompt):
    r = subprocess.run([CLAUDE_EXE, '-p', '--model', 'claude-fable-5'], input=prompt.encode('utf-8'),
                       capture_output=True, timeout=300)
    out = r.stdout.decode('utf-8', errors='ignore').strip()
    if r.returncode != 0 or not out: raise RuntimeError('fable 실패')
    return out


def main():
    os.makedirs(OUT_DIR, exist_ok=True)
    notes = {os.path.basename(p).replace('PATTERN-', '').replace('.md', ''): io.open(p, encoding='utf-8').read()
             for p in glob.glob(os.path.join(PATTERN_DIR, 'PATTERN-*.md'))}
    for key, symptom, refs in SYMPTOM_GROUPS:
        out_p = os.path.join(OUT_DIR, 'TREE-%s.md' % key)
        if os.path.exists(out_p):
            print('[tree] %s 이미 존재 — 건너뜀' % key); continue
        material = '\n\n'.join('### PATTERN-%s\n%s' % (r, notes[r][:3500]) for r in refs if r in notes)
        try:
            md = fable(PROMPT % (symptom, symptom, material))
            head = ('<!-- 자동 정제: tree_forge.py %s | 재료: 패턴노트 %s | CEO 검토 전 -->\n'
                    % (time.strftime('%Y-%m-%d'), ','.join(refs)))
            io.open(out_p, 'w', encoding='utf-8').write(head + md)
            print('[tree] 생성:', out_p)
        except Exception as e:
            print('[tree] 실패 %s: %s' % (key, str(e)[:80]))
    print('[tree] 완료 — 지식서버 /api/reload 필요')


if __name__ == '__main__':
    main()
