# -*- coding: utf-8 -*-
# dzw_qa_mass.py — SHIN QA 대량생성기 [CEO 설계 2026-07-06]
#   구조: 페이블 설계도(!_질문항목설계.md) × CEO 정답지(씨앗 GOLD·공정서·옵시디언) × 로컬 AI(Ollama)
#   옵시디언 신경망 경유: 문서 속 [[위키링크]]를 따라 이웃 노트 내용을 근거에 합쳐서 생성
#   출력: E:\VLA시스템\_QA뱅크\gen_<파일명>.qa.json  (지식서버가 로드 → /api/qa 즉답)
#   사용: python dzw_qa_mass.py            (GOLD 씨앗 + 공정서)
#         python dzw_qa_mass.py --all      (+ 옵시디언 도진웍스 노트)
import os, re, json, sys, urllib.request, glob

OUT   = r'E:\VLA시스템\_QA뱅크'
SPEC  = os.path.join(OUT, '!_질문항목설계.md')
SEEDS = r'E:\지식정제시스템\05_KNOWLEDGE\seeds'
VLA   = r'E:\VLA시스템'
OBS   = r'E:\옵시디언'
MODEL = 'llama3.1:8b'

def read(p):
    try: return open(p, encoding='utf-8', errors='ignore').read()
    except Exception: return ''

# 옵시디언 노트 인덱스 (파일명 → 경로) — [[링크]] 해석용
OBS_IDX = {}
def build_obs_index():
    for dp, dns, fns in os.walk(OBS):
        dns[:] = [d for d in dns if not d.startswith('.')]
        for fn in fns:
            if fn.endswith('.md'): OBS_IDX.setdefault(fn[:-3], os.path.join(dp, fn))

def neighbors(txt, limit=2, chars=1200):
    """위키링크 신경망 경유 — 문서가 가리키는 이웃 노트를 근거에 합류"""
    out = []
    for m in re.finditer(r'\[\[([^\]|#]+)', txt):
        p = OBS_IDX.get(m.group(1).strip())
        if p:
            out.append('\n[연결노트: %s]\n%s' % (m.group(1).strip(), read(p)[:chars]))
            if len(out) >= limit: break
    return ''.join(out)

def gen(name, txt, spec):
    ctx = txt[:6000] + neighbors(txt)
    prompt = (spec + '\n\n위 설계도의 카테고리 중 아래 문서 내용에 해당하는 것만 골라, '
              '현장 작업자 질문 Q:와 원문 근거 답 A:를 최대 20쌍 생성하라. '
              '원문에 없는 숫자·사실 금지. 각 답 끝에 (출처: ' + name + ') 표기.\n\n[문서]\n' + ctx)
    body = json.dumps({'model': MODEL, 'prompt': prompt, 'stream': False,
                       'options': {'temperature': 0.3, 'num_predict': 1600}}).encode()
    r = urllib.request.urlopen(urllib.request.Request(
        'http://localhost:11434/api/generate', data=body,
        headers={'Content-Type': 'application/json'}), timeout=600)
    qa, cur = [], None
    for line in json.loads(r.read()).get('response', '').splitlines():
        line = line.strip().lstrip('*-# ')
        if line.startswith('Q:'): cur = line[2:].strip()
        elif line.startswith('A:') and cur:
            a = line[2:].strip()
            if '출처' not in a: a += ' (출처: %s)' % name
            if len(a) > 15: qa.append({'q': cur, 'a': a})
            cur = None
    return qa

def sources(all_mode):
    # 1순위: CEO 검증 정답지 (GOLD 씨앗)
    for p in sorted(glob.glob(os.path.join(SEEDS, 'GOLD*.md'))): yield p
    # 2순위: 공정서·불량대책 원본
    for dp, _, fns in os.walk(VLA):
        if any(x in dp for x in ('EMBEDDINGS', 'MODELS', 'OUTPUT', '_QA뱅크')): continue
        for fn in fns:
            if fn.endswith('.md') and ('공정서' in fn or '불량' in fn) and '백업' not in fn:
                yield os.path.join(dp, fn)
    # 3순위(--all): 옵시디언 도진웍스 노트
    if all_mode:
        for dp, dns, fns in os.walk(os.path.join(OBS, '도진웍스')):
            dns[:] = [d for d in dns if not d.startswith('.')]
            for fn in fns:
                if fn.endswith('.md'): yield os.path.join(dp, fn)

def main():
    all_mode = '--all' in sys.argv
    spec = read(SPEC)
    if not spec: print('설계도 없음:', SPEC); return
    build_obs_index()
    print('[대량생성] 옵시디언 인덱스 %d노트, 모델 %s' % (len(OBS_IDX), MODEL))
    total = files = 0
    for p in sources(all_mode):
        name = os.path.basename(p)
        op = os.path.join(OUT, 'gen_' + name.replace('.md', '') + '.qa.json')
        if os.path.exists(op): continue   # 이어하기 (재실행 시 새 문서만)
        txt = read(p)
        if len(txt) < 200: continue
        try:
            qa = gen(name, txt, spec)
        except Exception as e:
            print('  [실패]', name, e); continue
        if qa:
            json.dump({'src': name, 'qa': qa}, open(op, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
            files += 1; total += len(qa)
            print('[%d] %s → %d문항 (누적 %d)' % (files, name, len(qa), total), flush=True)
    print('완료: %d문서 %d문항' % (files, total))

if __name__ == '__main__':
    main()
