diff --git a/03.Code/업로드용/converters/pipeline/step7_index.py b/03.Code/업로드용/converters/pipeline/step7_index.py deleted file mode 100644 index 4f40baf..0000000 --- a/03.Code/업로드용/converters/pipeline/step7_index.py +++ /dev/null @@ -1,504 +0,0 @@ -# -*- coding: utf-8 -*- -""" -make_outline.py - -기능: -- output_context/context/domain_prompt.txt -- output_context/context/corpus.txt -을 기반으로 목차를 생성하고, - -1) outline_issue_report.txt 저장 -2) outline_issue_report.html 저장 (테스트.html 레이아웃 기반 표 형태) -""" - -import os -import sys -import re -from pathlib import Path -from datetime import datetime -from typing import List, Dict, Any, Tuple - -from openai import OpenAI -from api_config import API_KEYS - -# ===== 경로 설정 ===== -DATA_ROOT = Path(r"D:\for python\geulbeot-light\geulbeot-light\00.test\hwpx\out") -OUTPUT_ROOT = Path(r"D:\for python\geulbeot-light\geulbeot-light\00.test\hwpx\out\out") # 출력 위치 -CONTEXT_DIR = OUTPUT_ROOT / "context" -LOG_DIR = OUTPUT_ROOT / "logs" - -for d in [CONTEXT_DIR, LOG_DIR]: - d.mkdir(parents=True, exist_ok=True) - -# ===== OpenAI 설정 (구조 유지) ===== -OPENAI_API_KEY = API_KEYS.get('GPT_API_KEY', '') -GPT_MODEL = "gpt-5-2025-08-07" - -client = OpenAI(api_key=OPENAI_API_KEY) - -# ===== 목차 파싱용 정규식 보완 (5분할 대응) ===== -RE_KEYWORDS = re.compile(r"(#\S+)") -RE_L1 = re.compile(r"^\s*(\d+)\.\s+(.+?)\s*$") -RE_L2 = re.compile(r"^\s*(\d+\.\d+)\s+(.+?)\s*$") -RE_L3 = re.compile(r"^\s*(\d+\.\d+\.\d+)\s+(.+?)\s*$") - -def log(msg: str): - print(msg, flush=True) - with (LOG_DIR / "make_outline_log.txt").open("a", encoding="utf-8") as f: - f.write(msg + "\n") - -def load_domain_prompt() -> str: - p = CONTEXT_DIR / "domain_prompt.txt" - if not p.exists(): - log("domain_prompt.txt가 없습니다. 먼저 domain_prompt.py를 실행해야 합니다.") - sys.exit(1) - return p.read_text(encoding="utf-8", errors="ignore").strip() - -def load_corpus() -> str: - p = CONTEXT_DIR / "corpus.txt" - if not p.exists(): - log("corpus.txt가 없습니다. 먼저 make_corpus.py를 실행해야 합니다.") - sys.exit(1) - return p.read_text(encoding="utf-8", errors="ignore").strip() - - -# 기존 RE_L1, RE_L2는 유지하고 아래 두 개를 추가/교체합니다. -RE_L3_HEAD = re.compile(r"^\s*(\d+\.\d+\.\d+)\s+(.+)$") -RE_L3_TOPIC = re.compile(r"^\s*[\-\*]\s+(.+?)\s*\|\s*(.+?)\s*\|\s*(\[.+?\])\s*\|\s*(.+)$") - -def generate_outline(domain_prompt: str, corpus: str) -> str: - sys_msg = { - "role": "system", - "content": ( - domain_prompt + "\n\n" - "너는 건설/측량 DX 기술 보고서의 구조를 설계하는 시니어 기술사이다. " - "주어진 corpus를 분석하여, 실무자가 즉시 활용 가능한 고밀도 지침서 목차를 설계하라." - ), - } - - user_msg = { - "role": "user", - "content": f""" -아래 [corpus]를 바탕으로 보고서 제목과 전략적 목차를 설계하라. - -[corpus] -{corpus} - -요구 사항: -1) 첫 줄에 보고서 제목 1개를 작성하라. -2) 그 아래 목차를 번호 기반 계측 구조로 작성하라. - - 대목차: 1. / 2. / 3. ... - - 중목차: 1.1 / 1.2 / ... - - 소목차: 1.1.1 / 1.1.2 / ... -3) **수량 제약 (중요)**: - - 대목차(1.)는 5~8개로 구성하라. - - **중목차(1.1) 하나당 소목차(1.1.1, 1.1.2...)는 반드시 2개에서 4개 사이로 구성하라.** (절대 1개만 만들지 말 것) - - 소목차(1.1.1) 하나당 '핵심주제(꼭지)'는 반드시 2개에서 3개 사이로 구성하라. - -[소목차 작성 형식] -1.1.1 소목차 제목 - - 핵심주제 1 | #키워드 | [유형] | 집필가이드(데이터/표 구성 지침) - - 핵심주제 2 | #키워드 | [유형] | 집필가이드(데이터/표 구성 지침) - -5) [유형] 분류 가이드: - - [비교형]: 기존 vs DX 방식의 비교표(Table)가 필수적인 경우 - - [기술형]: RMSE, GSD, 중복도 등 정밀 수치와 사양 설명이 핵심인 경우 - - [절차형]: 단계별 워크플로 및 체크리스트가 중심인 경우 - - [인사이트형]: 한계점 분석 및 전문가 제언(☞)이 중심인 경우 -6) 집필가이드는 50자 내외로, "어떤 데이터를 검색해서 어떤 표를 그려라"와 같이 구체적으로 지시하라. -7) 대목차는 최대 8개 이내로 구성하라. -""" - } - resp = client.chat.completions.create( - model=GPT_MODEL, - messages=[sys_msg, user_msg], - ) - return (resp.choices[0].message.content or "").strip() - - - -def parse_outline(outline_text: str) -> Tuple[str, List[Dict[str, Any]]]: - lines = [ln.rstrip() for ln in outline_text.splitlines() if ln.strip()] - if not lines: return "", [] - - title = lines[0].strip() # 첫 줄은 보고서 제목 - rows = [] - current_section = None # 현재 처리 중인 소목차(1.1.1)를 추적 - - for ln in lines[1:]: - raw = ln.strip() - - # 1. 소목차 헤더(1.1.1 제목) 발견 시 - m3_head = RE_L3_HEAD.match(raw) - if m3_head: - num, s_title = m3_head.groups() - current_section = { - "depth": 3, - "num": num, - "title": s_title, - "sub_topics": [] # 여기에 아래 줄의 꼭지들을 담을 예정 - } - rows.append(current_section) - continue - - # 2. 세부 꼭지(- 주제 | #키워드 | [유형] | 가이드) 발견 시 - m_topic = RE_L3_TOPIC.match(raw) - if m_topic and current_section: - t_title, kws_raw, t_type, guide = m_topic.groups() - # 키워드 추출 (#키워드 형태) - kws = [k.lstrip("#").strip() for k in RE_KEYWORDS.findall(kws_raw)] - - # 현재 소목차(current_section)의 리스트에 추가 - current_section["sub_topics"].append({ - "topic_title": t_title, - "keywords": kws, - "type": t_type, - "guide": guide - }) - continue - - # 3. 대목차(1.) 처리 - m1 = RE_L1.match(raw) - if m1: - rows.append({"depth": 1, "num": m1.group(1).strip(), "title": m1.group(2).strip()}) - current_section = None # 소목차 구간 종료 - continue - - # 4. 중목차(1.1) 처리 - m2 = RE_L2.match(raw) - if m2: - rows.append({"depth": 2, "num": m2.group(1).strip(), "title": m2.group(2).strip()}) - current_section = None # 소목차 구간 종료 - continue - - return title, rows - -def html_escape(s: str) -> str: - s = s or "" - return (s.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace('"', """) - .replace("'", "'")) - -def chunk_rows(rows: List[Dict[str, Any]], max_rows_per_page: int = 26) -> List[List[Dict[str, Any]]]: - """ - A4 1장에 표가 길어지면 넘치므로, 단순 행 개수로 페이지 분할한다. - """ - out = [] - cur = [] - for r in rows: - cur.append(r) - if len(cur) >= max_rows_per_page: - out.append(cur) - cur = [] - if cur: - out.append(cur) - return out - -def build_outline_table_html(rows: List[Dict[str, Any]]) -> str: - """ - 테스트.html의 table 스타일을 그대로 쓰는 전제의 표 HTML - """ - head = """ -
| 구분 | -번호 | -제목 | -키워드 | -
|---|---|---|---|
| 대목차 | -{num} | -{title} | -- |
| 중목차 | -{num} | -{title} | -- |
| 소목차 | -{num} | -{title} | -{kw} | -