📦 Initialize Geulbeot structure and merge Prompts & test projects

This commit is contained in:
2026-03-05 11:32:29 +09:00
commit 555a954458
687 changed files with 205247 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
def format_date(date_str, source):
try:
if source in ["국토일보", "한건신문"]:
# 기자 이름과 함께 있는 날짜 형식 처리
date_obj = re.search(r'\d{4}-\d{2}-\d{2}', date_str)
if date_obj:
return date_obj.group(0)
elif source in ["엔지니어링데일리", "건설이코노미뉴스", "공학저널"]:
# 기자 이름과 함께 있는 날짜 형식 처리
date_obj = re.search(r'\d{4}-\d{2}-\d{2}', date_str)
if date_obj:
return date_obj.group(0)
elif source == "연합

View File

@@ -0,0 +1,11 @@
def format_date(date_str: str, source: str) -> str:
"""날짜 형식을 YYYY-MM-DD 로 변환"""
try:
match = re.search(r'\d{4}-\d{2}-\d{2}', date_str)
if match:
return match.group(0)
if source == '연합뉴스':
return datetime.strptime(date_str, '%m-%d %H:%M').strftime('2024-%m-%d')
return date_str
except Exception:
return date_str

View File

@@ -0,0 +1,13 @@
def summarize_data_for(section: str):
texts = []
for path in sorted(os.listdir(DATA_DIR)):
with open(path, encoding="utf-8", errors="ignore") as f:
texts.append(f.read())
prompt = (
f"다음 로우데이터를 바탕으로 {section} 섹션에 들어갈 핵심 사실과 수치를 200~300자로 요약해주세요.\n\n"
+ "\n\n".join(texts)
)
return call_claude(prompt)
# ─── 4) 이미지 자동 매핑 ─────────────────────────

View File

@@ -0,0 +1,20 @@
def is_likely_unit(cell_val):
"""단위일 가능성 판별 (사용자 제안 로직)"""
if not cell_val:
return False
val = str(cell_val).strip()
# 1. 빈 값 또는 너무 긴 텍스트 (단위는 보통 6자 이내)
if not val or len(val) > 6:
return False
# 2. 순수 숫자는 제외
cleaned = val.replace('.', '').replace(',', '').replace('-', '').replace(' ', '')
if cleaned.isdigit():
return False
# 3. 수식은 제외
if val.startswith('='):
return False
# 4. 일반적인 계산 기호 및 정크 기호 제외

View File

@@ -0,0 +1,12 @@
def fetch_article_content(url: str, source: str) -> str:
"""단일 기사 본문 추출"""
try:
resp = requests.get(url, verify=False, timeout=10)
resp.encoding = 'utf-8'
resp.raise_for_status()
soup = BeautifulSoup(resp.text, 'html.parser')
paragraphs = soup.find_all('p')
content = ' '.join(clean_text(p.get_text()) for p in paragraphs)
content = content.replace('\n', ' ')
if not content.strip():
logging.warning(f'No content for

View File

@@ -0,0 +1,8 @@
def analyze_references():
files = sorted(os.listdir(REF_DIR))
sys = "당신은 보고서 전문가입니다. 아래 파일명들을 보고, 이 프로젝트에 어울리는 보고서 스타일과 목차 구조를 요약해 주세요."
usr = "파일 목록:\n" + "\n".join(files)
return call_gpt(sys, usr)
# ─── 2) 가이드라인에서 필수 섹션 추출 ───────────

View File

@@ -0,0 +1,13 @@
def run_global_reconstruction(input_file):
print("로그: 전체 시트 통합 데이터를 분석 중입니다...")
df = pd.read_excel(input_file)
# 1. 전역 주소록 생성: (시트명, 셀위치) -> 값
# 예: { ('A1', 'G105'): 30.901, ('철근집계', 'C47'): 159.263 }
global_map = {}
for _, row in df.iterrows():
global_map[(str(row['시트명']), str(row['셀위치']))] = row['현재값']
def trace_logic(formula, current_sheet):
if not isinstance(formula, str) or not formula.startswith("'="):
return formula

View File

@@ -0,0 +1,17 @@
def extract_all_contents(file_path):
print(f"로그: 파일을 읽는 중입니다 (전체 내용 모드)...")
# 수식과 값을 동시에 비교하기 위해 data_only=False로 로드
wb = openpyxl.load_workbook(file_path, data_only=False)
all_content_data = []
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
print(f"\n" + "="*60)
print(f"▶ 시트 탐색 중: [ {sheet_name} ]")
print("="*60)
# 시트의 모든 셀을 하나하나 검사
for row in ws.iter_rows():
for cell in row:
value = ce

View File

@@ -0,0 +1,18 @@
def fetch_articles(
base_url: str,
article_sel: str,
title_sel: str,
date_sel: str,
start_page: int,
end_page: int,
source: str,
url_prefix: str = '',
date_fmt_func=None
) -> list:
"""리스트 페이지 순회하며 메타데이터 및 본문 수집"""
results = []
for page in range(start_page, end_page + 1):
try:
page_url = f"{base_url}{page}"
resp = requests.get(page_url, verify=False, timeout=10)
soup = BeautifulSoup(resp.text, 'html.parser

View File

@@ -0,0 +1,11 @@
def get_item_id_with_lookback(ws, row, col, section_start_row):
"""멀티라인 대응 상향 번호 탐색 - 섹션 경계 존중"""
for r in range(row, section_start_row - 1, -1):
# 새로운 섹션을 만나면 탐색 중단
f_val_check = str(ws.cell(row=r, column=6).value or "").strip()
if r != row and re.match(r'^\(.*\)$|^\[.*\]$', f_val_check):
break
# F열에서 번호 탐색
if re.search(ID_MARKER_PATTERN, f_val_check):
return re.search(ID_MARKER_PATTERN, f_val_check).group()

View File

@@ -0,0 +1,14 @@
def collect_app_usage(days_back):
server = 'localhost'
log_type = 'Security'
hand = win32evtlog.OpenEventLog(server, log_type)
flags = win32evtlog.EVENTLOG_BACKWARDS_READ | win32evtlog.EVENTLOG_SEQUENTIAL_READ
usage_records = []
cutoff_date = datetime.datetime.now() - datetime.timedelta(days=days_back)
events = True
while events:
events = win32evtlog.ReadEventLog(hand, flags, 0)
for ev_obj in events:
event_time = ev_obj.TimeGenerated

View File

@@ -0,0 +1,11 @@
def extract_must_have_sections():
texts = []
for path in sorted(os.listdir(GUIDELINE_DIR)):
with open(path, encoding="utf-8", errors="ignore") as f:
texts.append(f.read())
sys = "법령·지침 문서를 바탕으로, 보고서에 반드시 들어가야 할 섹션(목차)을 순서대로 나열해 주세요."
usr = "\n\n---\n\n".join(texts)
return call_gpt(sys, usr).splitlines()
# ─── 3) 로우데이터에서 섹션별 내용 뽑기 ───────────

View File

@@ -0,0 +1,16 @@
def pick_images_for(section: str):
names = sorted(os.listdir(IMAGE_DIR))
prompt = (
f"보고서 {section} 섹션에 적합한 이미지를 아래 목록에서 1~2개 추천해 파일명만 리턴하세요:\n"
+ "\n".join(names)
)
resp = call_gpt("당신은 디자인 어시스턴트입니다.", prompt)
picked = []
for line in resp.splitlines():
fn = line.strip()
if fn in names:
picked.append(fn)
return picked
# ─── 5) 디자인 템플릿 선택 ───────────────────────

View File

@@ -0,0 +1,13 @@
class SslAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
ctx = ssl.create_default_context()
ctx.set_ciphers('DEFAULT:@SECLEVEL=1')
self.poolmanager = PoolManager(*args, ssl_context=ctx, **kwargs)
session = requests.Session()
session.mount('https://', SslAdapter())
headers = {'User-Agent': 'Mozilla/5.0', 'Accept-Language': 'ko-KR,ko;q=0.9'}
# -------------------------------------------------
# 사이트별 함수 (대한경제 제외)
# -----------------------------------

View File

@@ -0,0 +1,7 @@
def get_detail_content(detail_url):
res = requests.get(detail_url)
soup = BeautifulSoup(res.text, 'html.parser')
div = soup.find('div', {'data-v-5cb2d9fe': True})
if div and div.find('h2'):
return div.find('h2').text.strip()
return "설명이 없습니다."

View File

@@ -0,0 +1,11 @@
def fetch_dnews_articles(base_url, start_page, end_page):
# Selenium WebDriver 설정
options = webdriver.ChromeOptions()
options.add_argument('--headless') # 브라우저가 뜨지 않게 설정
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
# ChromeDriver 경로 설정
chromedriver_path = 'D:/python_for crawling/webdriver/chromedriver-win64/chromedriver.exe' # ChromeDriver 경로 설정
service = ChromeService(executable_path=chromedriver_path)
driver = webdr

View File

@@ -0,0 +1,17 @@
def extract_raw_constants(file_path):
# 수식 자체가 아닌 입력된 값을 확인하기 위해 로드
print(f"로그: 파일을 읽는 중입니다...")
wb = openpyxl.load_workbook(file_path, data_only=False)
raw_data = []
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
print(f"\n" + "="*50)
print(f"▶ [ {sheet_name} ] 시트의 원천 데이터(상수) 추출 시작")
print("="*50)
for row in ws.iter_rows():
for cell in row:
value = cell.value
coord = cell.coordin

View File

@@ -0,0 +1,11 @@
def reconstruct_formula(formula, wb_v, sheet_name):
"""수식 내 셀 주소를 실제 값으로 치환 및 기호 가독화"""
if not formula or not str(formula).startswith('='): return str(formula)
ref_pattern = r"(?:'([^']+)'|([a-zA-Z0-9가-힣]+))?!([A-Z]+\d+)|([A-Z]+\d+)"
def replace_with_value(match):
s_name = match.group(1) or match.group(2) or sheet_name
coord = match.group(3) or match.group(4)
try:
val = wb_v[s_name][coord].value
if val is None: return "0"

View File

@@ -0,0 +1,14 @@
def extract_excel_logic(file_path):
# 1. 수식을 가져오기 위한 로드 (data_only=False)
print(f"로그: 파일을 읽는 중입니다 (수식 모드)...")
wb_formula = openpyxl.load_workbook(file_path, data_only=False)
# 2. 결과값을 가져오기 위한 로드 (data_only=True)
print(f"로그: 파일을 읽는 중입니다 (데이터 모드)...")
wb_value = openpyxl.load_workbook(file_path, data_only=True)
extraction_data = []
for sheet_name in wb_formula.sheetnames:
ws_f = wb_formula[sheet_name]
ws_v = wb_value[sheet_name]

View File

@@ -0,0 +1,11 @@
def choose_design_template():
samples = sorted(os.listdir(DESIGN_DIR))
prompt = (
"아래 디자인 샘플 파일들 중 이 보고서에 어울리는 상위 3안(1안,2안,3안)을 "
"순서대로 파일명만으로 알려주세요:\n" + "\n".join(samples)
)
lines = call_gpt("디자인 전문가입니다.", prompt).splitlines()
return [ln.strip() for ln in lines if ln.strip() in samples][:3]
# ─── PPT 생성 ────────────────────────────────────

View File

@@ -0,0 +1,13 @@
def clean_text(text):
replacements = {
' ': ' ', '‘': "'", '’': "'", '“': '"', '”': '"',
'&amp;': '&', '&lt;': '<', '&gt;': '>', '&#39;': "'",
'&quot;' : "'", '&middot;': "'"
}
for entity, replacement in replacements.items():
text = text.replace(entity, replacement)
text = re.sub(r'<[^>]+>', '', text)
text = re.sub(r'\(엔지니어링데일리\).*?기자=', '', text) # (엔지니어링데일리) *** 기자= 패턴 삭제
text = re.sub(r'\[국토일보\s.*?

View File

@@ -0,0 +1,9 @@
def clean_text(text: str) -> str:
"""HTML 엔티티 및 불필요한 태그 제거"""
reps = {
'&nbsp;': ' ', '&lsquo;': "'", '&rsquo;': "'", '&ldquo;': '"', '&rdquo;': '"',
'&amp;': '&', '&lt;': '<', '&gt;': '>', '&#39;': "'", '&quot;': "'", '&middot;': "'"
}
for key, val in reps.items():
text = text.replace(key, val)
return re.sub(r'<[^>]+>', '', text).strip()

View File

@@ -0,0 +1,11 @@
def fetch_article_content(article_url, source):
try:
response = requests.get(article_url, verify=False, timeout=10) # SSL 인증서 검증 비활성화 및 타임아웃 설정
response.encoding = 'utf-8' # 인코딩 설정
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
paragraphs = soup.find_all('p')
content = ' '.join([clean_text(p.get_text()) for p in paragraphs])
# 텍스트 내의 엔터키를 스페이스로 대체
content = content.replace('\n', ' ')

View File

@@ -0,0 +1,14 @@
def get_category_and_content(detail_url):
res = requests.get(detail_url)
soup = BeautifulSoup(res.text, 'html.parser')
# 카테고리
category_tags = soup.select('ul.flex.flex-row.flex-wrap.gap-2 li a')
categories = [tag['href'].split('/')[-2] for tag in category_tags]
# 내용
content_div = soup.select_one('div.content-base.workflow-description.text-md')
if content_div:
content_text = content_div.get_text(separator=' ', strip=True)
else:
content_text =

View File

@@ -0,0 +1,14 @@
def build_ppt(sections, images_map, templates):
prs = Presentation()
prs.slide_width, prs.slide_height = Inches(8.27), Inches(11.69) # A4
# 커버 슬라이드
slide = prs.slides.add_slide(prs.slide_layouts[6])
tb = slide.shapes.add_textbox(Inches(1), Inches(2), Inches(6.27), Inches(2))
p = tb.text_frame.paragraphs[0]
p.text = "🚀 자동 보고서"
p.font.size = Pt(26); p.font.bold = True
# 본문 슬라이드
for sec in sections:
slide = prs.slides.add_slide(prs.slide_layouts[6]

View File

@@ -0,0 +1,15 @@
def find_unit_from_sum_cell(ws, sum_row, max_col):
"""
합계 셀 기준 단위 탐색
- 오른쪽 열 우선, 위쪽 방향 탐색
- 대분류 경계 무시 (합계 기준으로만 판단)
"""
# 오른쪽 열부터 왼쪽으로
for c in range(max_col, 0, -1):
# 합계 행부터 위쪽으로
for r in range(sum_row, 0, -1):
cell_val = ws.cell(row=r, column=c).value
if is_likely_unit(cell_val):
return str(cell_val).strip()
return ""

View File

@@ -0,0 +1,7 @@
<h3>3-2. 가계: 고금리 피크아웃, 하지만 체감 회복은 느리다</h3>
<p>기준금리는 정점을 지나 완만히 낮아지는 방향이지만, 과거 초저금리 시대와 비교하면 여전히 높은 수준이다.</p>
<ul>
<li>물가는 2022~2023년 고점에 비해 안정되었지만, 체감 물가는 여전히 높다.</li>
<li>임금 상승률은 개선되고 있지만, 금리와 물가를 감안한 실질소득 개선 속도는 빠르지 않다.</li>
<li>부동산은 급등기와 급락기를 지나 재조정 국면에 있으나, 지역·유형별 격차가 크다.</li>
</ul>

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>경제 진단 보고서</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;700&display=swap');

View File

@@ -0,0 +1,7 @@
<h3>2-4. 구조적 요인: 인구, 생산성, 부동산, 가계부채</h3>
<p>한국 경제의 장기 과제를 요약하면 인구, 생산성, 자산, 부채 네 가지 키워드로 정리할 수 있다.</p>
<p>첫째, 인구. 한국의 합계출산율은 0.7명 안팎으로 세계 최저 수준이다.</p>
<p>둘째, 생산성. 제조업 상위 기업들은 세계 최고 수준의 경쟁력을 유지하고 있지만, 중소기업, 서비스업, 지방 경제의 생산성은 상대적으로 정체되어 있다.</p>
<p>셋째, 부동산. 주택 가격은 일부 조정을 거쳤지만, 장기적으로 여전히 소득에 비해 높은 수준이다.</p>
<p>넷째, 가계부채. GDP 대비 가계부채 비율은 여전히 주요국 상위권이다.</p>
</section>

View File

@@ -0,0 +1,5 @@
<div class="shortcut-hint" id="shortcutHint">
<div><kbd>Ctrl</kbd>+<kbd>B</kbd> 굵게 | <kbd>Ctrl</kbd>+<kbd>I</kbd> 기울임 | <kbd>Ctrl</kbd>+<kbd>U</kbd> 밑줄</div>
<div><kbd>Ctrl</kbd>+<kbd>+</kbd> 자간↑ | <kbd>Ctrl</kbd>+<kbd>-</kbd> 자간↓ | <kbd>Ctrl</kbd>+<kbd>S</kbd> 저장</div>
<div style="margin-top: 5px; color: #00C853;">💡 본문 <kbd>Enter</kbd> → 새 문단 | <kbd>Backspace</kbd> → 병합</div>
</div>

View File

@@ -0,0 +1,6 @@
<div class="b-figure"></div>
<div class="b-caption">[그림] [캡션]</div>
</div>
</div>
`;
}

View File

@@ -0,0 +1,12 @@
/* 표, 그림은 전체폭이며 잘리지 않게 */
.sheet .body-content table,
.sheet .body-content figure{
width: 100%;
}
.sheet .body-content table th{
background: var(--b-light) !important;
color: var(--b-accent) !important;
}
`;
doc.head.appendChild(style);
}

View File

@@ -0,0 +1,8 @@
<section id="global-overview">
<h2>1. 글로벌 경제: 위기는 피했지만, 활력도 제한적인 “완만한 성장”</h2>
<h3>1-1. 성장률: 3%대 초반의 ‘애매한 회복’</h3>
<p>국제통화기금(IMF)은 2025년 세계 경제 성장률을 약 3.0~3.2% 수준으로 전망한다. 2024년 3.3%에서 2025년 3.2%, 2026년 3.1%로 완만하게 둔화되는 그림이다. 선진국은 1.5% 내외, 신흥국은 4%를 조금 웃도는 수준으로 양극화된 성장 구조가 이어질 것으로 평가된다.</p>
<p>큰 틀에서 보면 팬데믹 이후 “심각한 글로벌 침체” 시나리오는 피했지만, 세계은행이 지적하듯 향후 몇 년간의 성장률은 글로벌 금융위기 이후 평균보다도 낮은, “장기 저성장 트렌드”에 가까운 모습을 보이고 있다.</p>
<h3>1-2. 물가와 금리: 인플레이션은 진정, 그러나 금리는 과거보다 높은 수준에서 고착</h3>
<

View File

@@ -0,0 +1,6 @@
<div class="b-footer">
<span>[날짜]</span>
<span>[호수]</span>
</div>
</div>
</div>

View File

@@ -0,0 +1,5 @@
/* A, B의 대략 색감만 아주 약하게 */
.preview-a4.a .accent{ height:2px; background:#006400; opacity:0.55; margin:4px 0 6px; }
.preview-a4.b .accent{ height:2px; background:#e53935; opacity:0.55; margin:4px 0 6px; }
</style>
</head>

View File

@@ -0,0 +1,9 @@
/* 대제목(H1)은 전체폭 */
.sheet .body-content .b-top h1,
.sheet .body-content .b-col h1{
color: var(--b-primary) !important;
border-bottom: 2px solid var(--b-primary) !important;
margin: 0 0 10px 0;
font-size: 18pt;
font-weight: 900;
}

View File

@@ -0,0 +1,3 @@
<div class="b-lead">
[리드문] [리드문] [리드문]
</div>

View File

@@ -0,0 +1,5 @@
<h3>1-3. 리스크 요인: 무역갈등, 지정학, 고부채</h3>
<p>글로벌 전망에서 반복적으로 등장하는 키워드는 “정책 불확실성”이다. IMF는 2025년 4·10월 보고서에서, 관세 인상과 공급망 재편, 지정학적 긴장 고조가 향후 성장률을 추가로 깎아먹을 수 있는 하방 리스크라고 지적한다.</p>
<p>두 번째 리스크는 고부채다. 코로나 위기 대응 과정에서 확대한 정부지출과 이후의 고금리 환경이 결합되면서 많은 국가의 재정 상태가 빠르게 악화되었다.</p>
<p>마지막으로, 디지털 전환과 에너지 전환(탈탄소화)은 장기적으로는 성장 잠재력을 키우는 요인이지만, 단기적으로는 막대한 투자 비용과 산업 구조조정을 수반한다.</p>
</section>

View File

@@ -0,0 +1,11 @@
<script>
let isEditable = false;
let iframe = null;
let currentFileName = "report.html";
let currentReportTitle = "Report";
let activeBlock = null;
const historyStack = [];
const redoStack = [];
const MAX_HISTORY = 50;
let sourceHtml = ""; // 마지막으로 로드한 원본 HTML
let droppedAssets = new Map(); // 드롭된 부가 파일들(이미지 등) 이름 -> blob URL

View File

@@ -0,0 +1,3 @@
<div class="toast" id="toast">메시지</div>
<div class="loading" id="loading"><div class="spinner"></div><div>처리 중...</div></div>
<input type="file" id="fileInput" accept=".html,.htm" onchange="handleFile(event)">

View File

@@ -0,0 +1,10 @@
<!-- 목차 -->
<div class="preview-a4">
<div class="pad">
<div class="h1">[목차]</div>
<div class="toc-l1">1. [대목차]</div>
<div class="toc-l2">1.1 [중목차]</div>
<div class="toc-l3">1.1.1 [소목차]</div>
<div class="toc-l3">1.1.2 [소목차]</div>
<div class="toc-l1">2. [대목차]</div>
<div class="toc-

View File

@@ -0,0 +1,10 @@
<body>
<div class="app">
<aside class="sidebar">
<div class="sidebar-header">
<div class="logo">📝 문서 편집기 <span class="logo-sub">Word Style v2</span></div>
<button class="btn" onclick="openFile()">📂 파일 열기</button>
<button class="btn" onclick="openPasteModal()" style="border-color:#555;">📋 코드 붙여넣기</button>
</div>
<div class="sidebar-content">
<div style="padding: 8px; font-size: 11px; co

View File

@@ -0,0 +1,3 @@
<h3>2-2. 물가와 금리: 2% 초반 물가, 완화 기조에서 다시 “신중 모드”로</h3>
<p>한국의 물가는 2022~2023년 고물가 국면을 지나 2024년 이후 뚜렷한 안정세를 보였고, 2025년에는 2% 안팎에서 움직일 것이라는 전망이 우세하다.</p>
<p>한동안 완화 기조로 전환하던 한국은행은 최근 원화 약세와 다시 높아지는 물가 압력을 이유로 기준금리를 2.50% 수준에서 동결하고, “추가 인하에 매우 신중한 입장”으로 한 걸음 물러섰다.</p>

View File

@@ -0,0 +1,6 @@
function cloneMoveChildrenToList(doc, parentEl) {
if (!parentEl) return [];
const arr = [];
Array.from(parentEl.children).forEach(ch => arr.push(ch));
return arr;
}

View File

@@ -0,0 +1,7 @@
<div class="format-bar" id="formatBar">
<select class="format-select" id="fontFamily" onchange="applyFontFamily(this.value); restoreSelection();">
<option value="Noto Sans KR">본고딕</option>
<option value="Malgun Gothic">맑은 고딕</option>
<option value="serif">명조체</option>
</select>
<input type="number" class="format-select" id="fontSizeInput" value="12" min="6" max="72" style="widt

View File

@@ -0,0 +1,5 @@
<!-- 본문 1장(다단 인지) -->
<div class="preview-a4 b">
<div class="pad">
<div class="b-section">[대제목]</div>
<div class="b-subhead">[소제목]</div>

View File

@@ -0,0 +1,11 @@
<!-- 본문 -->
<div class="preview-a4">
<div class="pad">
<div class="h1">1. [대제목]</div>
<div class="h2">1.1 [중제목]</div>
<div class="p">[본문]</div>
<div class="p muted">[본문]</div>
<div class="h2">1.2 [중제목]</div>
<div class="p">[본문]</div>
<div class="p muted">[본문]</div>

View File

@@ -0,0 +1,13 @@
/* B 본문 레이아웃 */
.sheet .body-content .b-top{
margin: 0 0 10px 0;
}
.sheet .body-content .b-columns{
display: flex;
gap: 18px;
align-items: flex-start;
}
.sheet .body-content .b-col{
flex: 1;
min-width: 0;
}

View File

@@ -0,0 +1,8 @@
<div class="b-cols">
<div class="b-col">
<p>[본문]</p><p>[본문]</p><p>[본문]</p><p>[본문]</p><p>[본문]</p><p>[본문]</p>
</div>
<div class="b-col">
<p>[본문]</p><p>[본문]</p><p>[본문]</p><p>[본문]</p><p>[본문]</p><p>[본문]</p>
</div>
</div>

View File

@@ -0,0 +1,12 @@
/* 중, 소제목은 단 내부 */
.sheet .body-content .b-col h2{
color: var(--b-accent) !important;
border-left: 5px solid var(--b-primary) !important;
margin: 10px 0 6px 0;
font-size: 12pt;
font-weight: 900;
}
.sheet .body-content .b-col h3{
color: var(--b-primary) !important;
margin: 8px 0 4px 0;

View File

@@ -0,0 +1,3 @@
<h3>2-3. 수출과 산업: 반도체와 자동차가 버티는 가운데, 내수는 여전히 약한 고리</h3>
<p>2025년 한국 수출은 반도체와 자동차가 이끌고 있다. 특히 메모리 반도체 가격과 수요가 회복되면서, 11월 기준으로 반도체 수출은 전년 동기 대비 20%를 넘는 증가율을 보이고 있고, 자동차 역시 미국·유럽·신흥국에서 견조한 수요를 유지하고 있다.</p>
<p>내수 측면에서는 고금리 여파와 실질소득 둔화, 부동산 시장 조정으로 소비 심리가 완전히 회복되지 못했다. 한국은행 소비자동향지수(CCSI)는 팬데믹 직후보다는 높지만, 과거 확장 국면에 비하면 여전히 조심스러운 수준이다.</p>

View File

@@ -0,0 +1,6 @@
<div class="panel-body">
<div class="panel-section-title">양식 목록</div>
<div class="template-list" id="templateList"></div>
</div>
</aside>
</div>

View File

@@ -0,0 +1,9 @@
<div class="viewer">
<div id="viewerInner" style="height:100%; width:100%; display:flex; justify-content:center; transform-origin: top center;"></div>
</div>
</main>
<aside class="right-panel" id="templatePanel">
<div class="panel-header">
<div class="panel-title">양식 선택</div>
<button class="panel-add-btn" type="button" onclick="openTemplateAdd()">양식 추가</button>
</div>

View File

@@ -0,0 +1,3 @@
function openTemplateAdd() {
toast("양식 추가는 다음 단계에서 연결합니다");
}

View File

@@ -0,0 +1,12 @@
// Type A에서 검증된 제목+본문 그룹핑
function groupHeadingWithParagraph(nodes, index) {
const node = nodes[index];
if(!node) return { group: [], consumed: 0 };
const next = nodes[index + 1];
if(!node.tagName) return { group: [node], consumed: 1 };
const tag = node.tagName.toUpperCase();
if((tag === 'H2' || tag === 'H3') && next && next.tagName && next.tagName.toUpperCase() === 'P') {
return { group: [node

View File

@@ -0,0 +1,9 @@
/* 요약 박스 스타일 */
header p:last-of-type {
background-color: #f8f9fa;
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
font-weight: 500;
color: #555;
}

View File

@@ -0,0 +1,9 @@
<!-- 요약 -->
<div class="preview-a4">
<div class="pad">
<div class="h1">[요약]</div>
<div class="p">[요약 내용 1]</div>
<div class="p">[요약 내용 2]</div>
<div class="p">[요약 내용 3]</div>
</div>
</div>

View File

@@ -0,0 +1,9 @@
</head>
<body>
<div class="a4-wrapper">
<header>
<h1>요즘 경제, 어디까지 왔나: 2025년 말 글로벌·한국 경제 진단</h1>
<p>작성일: 2025년 11월 27일</p>
<p>요약: 고물가 쇼크와 급격한 금리 인상 국면이 지나가고 있지만, 세계 경제는 저성장·고부채·지정학 리스크라는 세 가지 부담을 안고 완만한 성장세에 머물고 있다. 한국 경제 역시 1%대 저성장과 수출 의존 구조, 인구·부동산·가계부채 문제라는 고질적 구조적 과제를 동시에 마주하고 있다.</p>
</header>

View File

@@ -0,0 +1,6 @@
/* --- 인쇄 모드 (PDF 변환 시 적용) 핵심 코드 --- */
@media print {
@page {
size: A4;
margin: 0; /* 브라우저 여백 제거하고 body padding으로 제어 */
}

View File

@@ -0,0 +1,9 @@
<main class="main">
<div class="toolbar">
<div class="status-badge" id="editStatusBadge">
<div class="dot"></div> <span id="editStatusText">읽기 전용</span>
</div>
<div class="toolbar-divider"></div>
<button class="toolbar-btn" id="btnEdit" onclick="toggleEditMode()">✏️ 편집</button>
<button class="toolbar-btn" title="실행 취소" onclick="performUndo()">↩️</button>
<button

View File

@@ -0,0 +1,3 @@
<footer>
<p>자료 출처: IMF World Economic Outlook(2025년 4·10월판), World Bank Global Economic Prospects(2025년 6월), OECD Economic Outlook(2025년 6월 및 9월 업데이트), 한국은행, KDI, 국내 주요 언론 경제면 정리.</p>
</footer>

View File

@@ -0,0 +1,9 @@
:root {
--font-main: 'Noto Sans KR', sans-serif;
--page-width: 210mm;
--page-height: 297mm;
--margin-top: 25mm;
--margin-bottom: 25mm;
--margin-side: 25mm;
--primary-color: #003366; /* 전문적인 네이비 컬러 */
}

View File

@@ -0,0 +1,4 @@
<h3>3-4. 정리: “위기는 완화, 과제는 심화”된 국면</h3>
<p>요약하면, 2025년 말 세계와 한국 경제는 “급한 불은 껐지만, 구조적 과제는 더욱 분명해진 상태”라고 정리할 수 있다.</p>
<p>지금의 경제 상황은 “모든 것이 나쁘다”기보다는, “잘하는 영역과 못하는 영역의 차이가 점점 더 크게 벌어지는 시대”에 가깝다.</p>
</section>

View File

@@ -0,0 +1,3 @@
<div class="b-title">
[제목] <span class="red">[강조]</span> [제목]
</div>

View File

@@ -0,0 +1,10 @@
h2 {
font-size: 16pt;
color: #0056b3;
margin-top: 25px;
margin-bottom: 15px;
border-left: 5px solid #0056b3;
padding-left: 10px;
break-after: avoid; /* 제목 뒤에서 페이지 넘김 방지 */
break-inside: avoid;
}

View File

@@ -0,0 +1,4 @@
h2, h3 {
break-after: avoid; /* 제목만 덩그러니 남는 것 방지 */
page-break-after: avoid;
}

View File

@@ -0,0 +1,13 @@
/* 줄바꿈 시 다음줄 들여쓰기(넘버링 정렬) */
.sheet .body-content .b-top h1,
.sheet .body-content .b-col h1{
padding-left: 24px;
text-indent: -24px;
}
.sheet .body-content .b-col h2{
padding-left: 20px;
text-indent: -20px;
}
.sheet .body-content .b-col h3{
padding-left: 18px;
text-in

View File

@@ -0,0 +1,9 @@
<section id="implications">
<h2>3. 지금 경제 상황이 의미하는 것: 기업과 가계, 투자자의 관점</h2>
<h3>3-1. 기업: “고성장 시대”가 아니라 “정교한 선택의 시대”</h3>
<p>세계와 한국 모두 고성장 국면은 아니다. 대신, 성장의 편차와 업종 간 차별화가 중요한 시대다.</p>
<ul>
<li>디지털 전환과 자동화를 통해 인력·자본 효율성을 높이고, 비용 구조를 경직적에서 유연한 형태로 바꾸는 것</li>
<li>해외 시장과 공급망 다변화를 통해 특정 국가·산업 의존도를 줄이는 것</li>
<li>R&D와 데이터 활용을 통해 제품·서비스 차별화, 고부가가치화를 추구하는 것</li>
</ul>

View File

@@ -0,0 +1,9 @@
/* --- 타이포그래피 설정 --- */
h1 {
font-size: 24pt;
color: var(--primary-color);
border-bottom: 3px solid var(--primary-color);
padding-bottom: 10px;
margin-bottom: 30px;
break-after: avoid; /* 제목 뒤에서 페이지 넘김 방지 */
}

View File

@@ -0,0 +1,7 @@
<h3>3-3. 투자자: “불확실성 관리”가 기본값이 된 시장</h3>
<p>주식, 채권, 부동산, 대체투자 모두에서 공통적으로 나타나는 특징은 “중간 정도의 성장과 반복되는 정책 변수”다.</p>
<ul>
<li>국가·통화·자산군 분산을 통한 리스크 헤지</li>
<li>특정 테마(예: AI, 친환경, 헬스케어)에 대한 집중 투자 여부를, 수익·현금흐름·규제 환경을 종합적으로 보며 판단하는 것</li>
<li>단기 금리·환율 이벤트에 휘둘리기보다는, 3~5년 이상의 중기적 시계에서 정책과 구조 변화의 방향성을 읽는 것</li>
</ul>

View File

@@ -0,0 +1,9 @@
<div class="drop-overlay" id="dropOverlay"><div class="drop-box">📄 파일을 놓으세요</div></div>
<div class="modal-overlay" id="pasteModal">
<div class="modal-box">
<div class="modal-header">📋 HTML 코드 붙여넣기</div>
<textarea class="modal-textarea" id="pasteInput" placeholder="HTML 코드를 여기에 붙여넣으세요..."></textarea>
<div class="modal-footer">
<button class="btn" style="width:auto;" onclick="closePasteModal()">취소</button>
<bu

View File

@@ -0,0 +1,5 @@
function applyTemplate(templateId) {
if (!iframe) {
toast("파일을 먼저 열어주세요");
return;
}

View File

@@ -0,0 +1,9 @@
p, ul, li {
orphans: 2; /* 페이지 끝에 한 줄만 남는 것 방지 */
widows: 2; /* 다음 페이지에 한 줄만 넘어가는 것 방지 */
}
/* 문단이 너무 작게 잘리는 것을 방지 */
p {
break-inside: auto;
}

View File

@@ -0,0 +1,4 @@
/* 페이지 분할 알고리즘 */
section {
break-inside: auto; /* 섹션 내부에서 페이지 나눔 허용 */
}

View File

@@ -0,0 +1,12 @@
function buildPreviewA() {
return `
<!-- 표지 -->
<div class="preview-a4">
<div class="pad">
<div class="cover-top">[날짜]<br>[작성자]</div>
<div class="cover-center">
<div class="cover-title">[제목]</div>
<div class="cover-sub">[부제]</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,16 @@
const TEMPLATE_REGISTRY = [
{
id: "A",
name: "A type",
desc: "표지 목차 요약 본문 구조",
preview: () => buildPreviewA(),
apply: (doc) => applyTemplateA(doc),
},
{
id: "B",
name: "B type",
desc: "다단 본문 중심 구조",
preview: () => buildPreviewB(),
apply: (doc) => applyTemplateB(doc),
},
];

View File

@@ -0,0 +1,7 @@
function buildPreviewB() {
return `
<!-- 표지 1장 -->
<div class="preview-a4 b">
<div class="pad">
<div class="b-tag">[특집]</div>
<div class="b-pill">[브랜드]</div>

View File

@@ -0,0 +1,6 @@
/* 표지나 특정 섹션 강제 넘김이 필요하면 사용 */
.page-break {
break-before: page;
}
}
</style>

View File

@@ -0,0 +1,8 @@
/* 푸터 (출처) 스타일 */
footer {
margin-top: 30px;
border-top: 1px solid #eee;
padding-top: 10px;
font-size: 9pt;
color: #888;
}

View File

@@ -0,0 +1,5 @@
<section id="korea-economy">
<h2>2. 한국 경제: 1%대 성장과 수출 회복 사이에서 균형 찾기</h2>
<h3>2-1. 성장률: 2025년 1% 안팎, 2026년 2%대 회복 전망</h3>
<p>OECD는 한국의 2025년 실질 GDP 성장률을 약 1.0% 수준으로, 2026년에는 2.2%까지 완만하게 회복될 것으로 전망한다. 세계 평균 성장률(3%대 초반)과 비교하면 상당히 낮은 수준으로, 저성장 구조가 점점 고착화되고 있다는 평가가 많다.</p>
<p>한국은행과 국내 연구기관(KDI 등) 역시 비슷한 그림을 제시한다. 대외 환경이 크게 악화되지는 않았지만, 수출과 설비투자는 제한적 회복에 그치고, 소비와 건설투자가 경제를 강하게 끌어올릴 만큼의 모멘텀을 보여주지 못한다는 판단이다.</p>

View File

@@ -0,0 +1,10 @@
body {
font-family: var(--font-main);
background-color: #f5f5f5; /* 화면 확인용 회색 배경 */
margin: 0;
padding: 20px;
color: #333;
line-height: 1.6;
word-break: keep-all; /* 단어 단위 줄바꿈 (가독성 핵심) */
text-align: justify;
}

View File

@@ -0,0 +1,11 @@
/* 화면에서 볼 때 A4처럼 보이게 하는 컨테이너 */
.a4-wrapper {
width: var(--page-width);
min-height: var(--page-height);
background: white;
margin: 0 auto;
padding: var(--margin-top) var(--margin-side) var(--margin-bottom) var(--margin-side);
box-shadow: 0 0 15px rgba(0,0,0,0.1);
box-sizing: border-box;
position: relative;
}

View File

@@ -0,0 +1 @@
}

View File

@@ -0,0 +1,5 @@
response = client.chat.completions.create(
gpt-4.1-2025-04-14,
messages=[{role: user, content: f다음을 한국어로 번역해줘:\n{text}}]
)
return response.choices[0].message.content.strip()

View File

@@ -0,0 +1,9 @@
resp = openai.ChatCompletion.create(
model=model,
messages=[
{role:system, content: system_prompt},
{role:user, content: user_prompt}
],
temperature=0.7
)
return resp.choices[0].message.content.strip()

View File

@@ -0,0 +1,15 @@
response = client.chat.completions.create(
gpt-4.1-2025-04-14,
messages=[{role: user, content: f아래 내용을 50자 내외로 요약해줘:\n{text}}]
)
return response.choices[0].message.content.strip()
for category in categories:
driver.get(base_url.format(category))
time.sleep(2)
# Load
while True:
try:
load_more = driver.find_element(By.XPATH, //button[contains(text(),'Load more templates')])