diff --git a/scripts/assemble_stage2.py b/scripts/assemble_stage2.py index e4c501c..2658e25 100644 --- a/scripts/assemble_stage2.py +++ b/scripts/assemble_stage2.py @@ -877,11 +877,50 @@ def _assemble_type_b(run: Path, ctx: dict): clean = bold(clean, rn) bullets += f'
{clean}
\n' + # X'-6: 본문 표 요약이 있으면 하단 우측에 추가 + table_summaries = enh.get("table_summaries", {}) + table_html_br = "" + for ts_key, ts_data in table_summaries.items(): + fmt = ts_data.get("format", "text") + if fmt == "table": + cols = ts_data.get("columns", []) + data = ts_data.get("data", []) + col_count = len(cols) + if col_count > 0 and data: + header_cells = "".join( + f'
{c}
' + for c in cols + ) + rows_html = "" + for ri, row in enumerate(data): + bg = "#f8fafc" if ri % 2 == 0 else "#fff" + cells = "" + for ci, cell in enumerate(row): + c_color = "#1e40af" if ci == 0 else "#475569" + c_weight = "600" if ci == 0 else "400" + cells += f'
{bold(str(cell), rn)}
' + rows_html += f'
{cells}
\n' + table_html_br = ( + f'
' + f'
{header_cells}
' + f'{rows_html}
' + ) + elif fmt == "bullets": + items = ts_data.get("items", []) + table_html_br = "".join( + f'
• {bold(str(item), rn)}
' + for item in items + ) + elif fmt == "text": + table_html_br = f'
{bold(str(ts_data.get("summary", "")), rn)}
' + br_html = ( - f'
' + f'
' f'{popup_html_br}' f'
{topic_title}
' - f'
{bullets}
' + f'
{bullets}
' + f'{table_html_br}
' ) # 결론 diff --git a/src/pipeline.py b/src/pipeline.py index 8480c35..7ce8674 100644 --- a/src/pipeline.py +++ b/src/pipeline.py @@ -780,6 +780,41 @@ async def generate_slide( popup_summaries[pr] = summary logger.info(f"[V'-2] {pr}: format={summary.get('format')}") + # X'-6: 본문 표 요약 (유형 B — normalized.tables가 있으면) + table_summaries = {} + norm_tables = context.normalized.tables or [] + if norm_tables and context.analysis.layout_template == "B": + from src.kei_client import call_kei_summarize_popup + for ti, table_data in enumerate(norm_tables): + headers = table_data.get("headers", []) + rows = table_data.get("rows", []) + if not headers or not rows: + continue + # 표를 마크다운 형태로 변환하여 Kei에게 전달 + md_table = "| " + " | ".join(headers) + " |\n" + md_table += "| " + " | ".join(["---"] * len(headers)) + " |\n" + for row in rows: + md_table += "| " + " | ".join(str(c) for c in row) + " |\n" + + # 하단 우측 공간 계산 + bottom_roles = [r for r, ci in updated_containers.items() if ci.zone in ("bottom_left", "bottom_right")] + if bottom_roles: + br_ci = next((ci for r, ci in updated_containers.items() if ci.zone == "bottom_right"), None) + if br_ci: + available_h = br_ci.height_px - 30 # 제목 + padding + available_w = br_ci.width_px + fs = font_h.get("core", 12) + summary = await call_kei_summarize_popup( + popup_title=f"본문표{ti+1}", + popup_content=md_table, + available_width_px=available_w, + available_height_px=available_h, + font_size=fs, + ) + if summary: + table_summaries[f"table_{ti}"] = summary + logger.info(f"[X'-6] 본문표{ti+1}: format={summary.get('format')}") + # 결과를 context에 저장 (Stage 2에서 사용) return { "containers": updated_containers, @@ -811,6 +846,7 @@ async def generate_slide( "emphasis_blocks": enhancements.emphasis_blocks, "bold_keywords": enhancements.bold_keywords, "popup_summaries": popup_summaries, + "table_summaries": table_summaries, }, }