# -*- coding: utf-8 -*-
r"""
pattern_forge.py — 장인 암묵지 패턴 추출기 [CEO 2026-07-18]

CEO 설계 사상:
  장인 = 비슷한 문제 순간을 수천 번 겪으며 패턴을 찾고, 새 작업이 와도 그 경험치로
  원인을 단숨에 파악한다. 제품이 달라도(기아 타이어↔벤츠 타이어) 기능이 같으면 핵심은 이전된다.

동작:
  1) 씨앗·공정서 전체에서 "문제 순간" 문단을 수집 (갭·변형·기공·언더컷·크랙·스패터·용락·오차…)
  2) 문제 주제별로 묶되, **서로 다른 제품 2개 이상에서 반복된 주제만** 패턴 후보로 인정
     (한 제품에서만 나온 건 아직 '경험'이지 '패턴'이 아니다)
  3) 페이블이 패턴 씨앗으로 정제: 상황 신호 → 원인 후보 순위 → 확인 순서 → 해법 → 제품별 차이 → 출처
  4) E:\VLA시스템\KNOWLEDGE\_패턴씨앗\PATTERN-주제.md 저장 → 지식서버가 자동 인덱스
     → 새 제품 질문이 와도 SHIN이 패턴부터 참조

실행: python pattern_forge.py           (기본 상한 8개 패턴/회 — 페이블 비용 절제)
      python pattern_forge.py --list    (패턴 후보 주제와 사례 수만 출력, 페이블 호출 없음)
"""
import os, re, json, sys, subprocess, time

SRC_ROOTS = [r'E:\VLA시스템', r'E:\지식정제시스템\05_KNOWLEDGE\seeds']
OUT_DIR = r'E:\VLA시스템\KNOWLEDGE\_패턴씨앗'
CLAUDE_EXE = os.path.expanduser(r'~\.local\bin\claude.exe')
MAX_PATTERNS = 8

# 문제 순간 주제 사전 — (주제키, 감지 정규식)
THEMES = [
    ('갭관리',     r'갭|간극|틈새|벌어'),
    ('열변형',     r'변형|뒤틀|휨|수축|비틀'),
    ('기공',       r'기공|블로우홀|포로시티'),
    ('언더컷',     r'언더컷|파임'),
    ('크랙',       r'크랙|균열|터짐'),
    ('스패터',     r'스패터|튐'),
    ('용락',       r'용락|번스루|구멍'),
    ('용입불량',   r'용입\s*불량|융합\s*불량|미용착'),
    ('비드불량',   r'비드\s*(불량|형상|처짐|불균일)|오버랩'),
    ('취부오차',   r'취부.{0,6}(오차|틀어|어긋|불량)|가접.{0,6}(오차|불량|틀어)'),
    ('치수오차',   r'치수.{0,6}(오차|틀|공차)|공차\s*이탈'),
    ('전류전압',   r'전류.{0,10}(조정|낮추|올리|보정)|전압.{0,10}(조정|낮추|올리|보정)'),
    ('토치각도',   r'토치\s*(각도|자세|기울)|작업각|진행각'),
    ('이종두께',   r'이종\s*두께|두께\s*차|박판.{0,8}후판'),
    ('절단면품질', r'절단면|개선면|산화막|드로스'),
]
PROBLEM_CUE = r'문제|불량|원인|주의|실패|대응|해결|방지|금지|조심|팁|노하우|경험'


def product_of(path):
    """경로에서 제품(폴더) 식별 — 패턴은 서로 다른 제품 2개 이상에서 반복돼야 인정"""
    parts = path.replace('/', '\\').split('\\')
    for i, seg in enumerate(parts):
        if seg in ('seeds', 'KNOWLEDGE', 'knowledge') and i + 2 < len(parts):
            return '\\'.join(parts[i + 1:i + 3])
    return parts[-2] if len(parts) >= 2 else path


def collect():
    cases = {k: [] for k, _ in THEMES}
    for root in SRC_ROOTS:
        for dp, _, fns in os.walk(root):
            if any(x in dp for x in ('EMBEDDINGS', 'MODELS', 'OUTPUT', 'FINETUNE', '_패턴씨앗', '_QA뱅크')):
                continue
            for fn in fns:
                if not fn.endswith('.md'): continue
                p = os.path.join(dp, fn)
                try: txt = open(p, encoding='utf-8', errors='ignore').read()
                except Exception: continue
                # 문단 단위로 문제 순간 채집
                for para in re.split(r'\n\s*\n', txt):
                    if len(para) < 60 or not re.search(PROBLEM_CUE, para): continue
                    for key, pat in THEMES:
                        if re.search(pat, para):
                            cases[key].append({'src': p, 'product': product_of(p), 'text': para.strip()[:900]})
    return cases


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


# [CEO 2026-07-19 피드백] "앞뒤전후 없이 코드만 던지면 내가 썼어도 못 알아본다" — 사람이 읽는 문서로:
#  서문 필수, 도번/코드엔 쉬운 말 병기, 완결된 문장, 각 항목이 '왜'까지 설명.
PAT_PROMPT = ('너는 용접·제관 30년 장인의 암묵지를 정리하는 정제자다. 아래는 서로 다른 제품에서 반복 관찰된 '
              '"%s" 문제 순간 사례들이다. 장인이 머릿속에서 하듯 패턴으로 정제하되, '
              '**용접을 모르는 사람이 읽어도 앞뒤가 이해되는 완결된 문서**로 써라.\n'
              '문서 규칙:\n'
              '- 맨 위에 서문 3~5문장: 이 문서가 무엇인지, 어떤 기록 몇 건에서 뽑았는지, 현장에서 언제 꺼내 보는 문서인지.\n'
              '- 도번·코드·전문용어는 반드시 쉬운 말 병기. 예: "RD202-0008(트랙서포트 부품의 가접 실패 기록)". 코드만 덜렁 쓰지 마라.\n'
              '- 각 항목은 완결된 문장으로, "무엇을 왜"까지. 개조식 나열만 하지 마라.\n'
              '- [CEO 지시] 모든 사례 인용은 반드시 이야기 순서로: ①어떤 제품의 어떤 부위를 ②어떤 조건/상황에서 작업하다가 '
              '③무슨 일이 생겼고 ④그래서 어떤 결론이 나왔는지. 전후 맥락 없이 결론 문장만 던지는 것 금지. '
              '사례 기록에 조건이 안 적혀 있으면 "기록에는 조건이 남아 있지 않다"라고 정직하게 쓰라.\n'
              '- 사례에 있는 사실·수치만 사용(지어내기 금지). 사례가 상충하면 둘 다 쓰고 [CEO확인] 표시.\n'
              '- 제품이 달라도 통하는 공통 핵심과, 제품마다 달라지는 부분을 구분.\n'
              '출력 형식(마크다운):\n'
              '# %s 문제 — 현장 패턴 노트\n> (서문)\n## 1. 이런 신호가 보이면 이 문제를 의심하라\n'
              '## 2. 원인 — 가능성 높은 순서로\n## 3. 확인 순서 — 장인이 먼저 보는 곳부터\n'
              '## 4. 대응 방법\n## 5. 제품마다 다른 점\n## 6. 이 문서의 근거가 된 기록들\n\n[사례들]\n%s')


def main():
    only_list = '--list' in sys.argv
    cases = collect()
    cands = []
    for key, _ in THEMES:
        arr = cases[key]
        prods = {c['product'] for c in arr}
        if len(arr) >= 3 and len(prods) >= 2:      # 패턴 인정 조건: 사례 3+, 제품 2+
            cands.append((key, arr, prods))
    cands.sort(key=lambda x: -len(x[1]))
    print('[pattern] 패턴 후보 %d개 (전체 주제 %d개 중)' % (len(cands), len(THEMES)))
    for key, arr, prods in cands:
        print('  - %s: 사례 %d건 / 제품 %d종' % (key, len(arr), len(prods)))
    if only_list: return
    os.makedirs(OUT_DIR, exist_ok=True)
    made = 0
    for key, arr, prods in cands:
        if made >= MAX_PATTERNS: break
        out_p = os.path.join(OUT_DIR, 'PATTERN-%s.md' % key)
        if os.path.exists(out_p):
            print('[pattern] %s — 이미 존재, 건너뜀 (갱신은 파일 삭제 후 재실행)' % key); continue
        # 사례 표본: 제품 다양성 우선으로 최대 12건
        seen_prod, sample = {}, []
        for c in sorted(arr, key=lambda c: c['product']):
            if seen_prod.get(c['product'], 0) >= 3: continue
            seen_prod[c['product']] = seen_prod.get(c['product'], 0) + 1
            sample.append('(%s | %s)\n%s' % (c['product'], os.path.basename(c['src']), c['text']))
            if len(sample) >= 12: break
        try:
            md = fable(PAT_PROMPT % (key, key, '\n\n'.join(sample)))
            head = ('<!-- 자동 정제: pattern_forge.py %s | 사례 %d건, 제품 %d종 | CEO 검토 전 -->\n'
                    % (time.strftime('%Y-%m-%d'), len(arr), len(prods)))
            open(out_p, 'w', encoding='utf-8').write(head + md)
            made += 1
            print('[pattern] 생성: %s (사례 %d건)' % (out_p, len(arr)))
        except Exception as e:
            print('[pattern] 실패 %s: %s' % (key, str(e)[:80]))
    print('[pattern] 완료 — %d개 생성. 지식서버 /api/reload 하면 SHIN이 즉시 사용.' % made)


if __name__ == '__main__':
    main()
