Generate separate slide outputs for three DX MDX inputs

This commit is contained in:
2026-04-03 16:27:17 +09:00
parent bebb627873
commit e4c90d63b9
119 changed files with 6923 additions and 87 deletions

View File

@@ -378,6 +378,65 @@ def _details_blocks(raw: str) -> list[str]:
return re.findall(r'<details>(.*?)</details>', raw, flags=re.S)
def _popup_list_html(items: list[str], floor: int = 90, ceiling: int = 260) -> str:
if not items:
return '<div style="font-size:12px; color:#64748b;">??? ?? ??? ????.</div>'
lis = ''.join(
f'<li style="margin-left:18px; margin-bottom:8px; line-height:1.55;">{_trim_visible_copy(item, floor=floor, ceiling=ceiling)}</li>'
for item in items
)
return f'<ul style="margin:0; padding-left:0; list-style:disc; font-size:13px; color:#334155;">{lis}</ul>'
def _popup_comparison_table(rows: list[tuple[str, str, str]]) -> str:
if not rows:
return '<div style="font-size:12px; color:#64748b;">??? ???? ????.</div>'
body = ''.join(
'<tr>'
f'<td style="padding:10px 12px; border-top:1px solid #e2e8f0; font-size:12px; line-height:1.5; color:#1e3a8a; vertical-align:top;">{_trim_visible_copy(dx, floor=160, ceiling=420)}</td>'
f'<td style="padding:10px 8px; border-top:1px solid #e2e8f0; font-size:12px; line-height:1.35; color:#1d4ed8; font-weight:800; background:#eff6ff; text-align:center; vertical-align:top;">{axis}</td>'
f'<td style="padding:10px 12px; border-top:1px solid #e2e8f0; font-size:12px; line-height:1.5; color:#475569; vertical-align:top;">{_trim_visible_copy(bim, floor=160, ceiling=420)}</td>'
'</tr>'
for axis, dx, bim in rows
)
return (
'<div style="border:1px solid #cfe0ff; border-radius:12px; overflow:hidden;">'
'<table style="width:100%; border-collapse:collapse; table-layout:fixed;">'
'<thead><tr style="background:linear-gradient(135deg,#0d47a1,#1565c0); color:#fff;">'
'<th style="padding:10px 12px; font-size:13px;">DX</th>'
'<th style="padding:10px 8px; font-size:13px; width:92px;">??</th>'
'<th style="padding:10px 12px; font-size:13px;">BIM</th>'
'</tr></thead>'
f'<tbody>{body}</tbody></table></div>'
)
def _popup_button(button_id: str, label: str) -> str:
return (
f"<button type=\"button\" onclick=\"document.getElementById('{button_id}').style.display='flex'\" "
'style="margin-top:6px; border:none; background:#eff6ff; color:#1d4ed8; border:1px solid #bfdbfe; '
'border-radius:999px; padding:4px 10px; font-size:10px; font-weight:700; cursor:pointer;">'
f'{label}</button>'
)
def _popup_overlay(popup_id: str, title: str, content_html: str) -> str:
return (
f'<div id="{popup_id}" style="display:none; position:fixed; inset:0; background:rgba(15,23,42,0.56); '
'align-items:center; justify-content:center; z-index:9999; padding:28px;">'
'<div style="width:min(920px, 92vw); max-height:88vh; overflow:auto; background:#ffffff; border-radius:18px; '
'box-shadow:0 24px 80px rgba(15,23,42,0.28); padding:22px 24px 20px 24px;">'
'<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:14px;">'
f'<div style="font-size:20px; font-weight:900; color:#0f172a;">{title}</div>'
f"<button type=\"button\" onclick=\"document.getElementById('{popup_id}').style.display='none'\" "
'style="border:none; background:#e2e8f0; color:#334155; width:34px; height:34px; border-radius:999px; '
'font-size:18px; font-weight:800; cursor:pointer;">?</button>'
'</div>'
f'{content_html}'
'</div></div>'
)
def _evidence_bullets_from_raw(raw: str) -> list[str]:
blocks = _details_blocks(raw)
if not blocks:
@@ -497,19 +556,20 @@ def _build_stage2_retry_html(ctx: PipelineContext, retry_plan: dict) -> dict:
evidence_topic = _topic(ctx, 4)
comparison_topic = _topic(ctx, 5)
problem_title = problem_topic.title if problem_topic and problem_topic.title else 'Problem'
definitions_title = definitions_topic.title if definitions_topic and definitions_topic.title else 'Definitions'
relation_title = relation_topic.title if relation_topic and relation_topic.title else 'Relationship'
evidence_title = evidence_topic.title if evidence_topic and evidence_topic.title else 'Evidence'
comparison_title = comparison_topic.title if comparison_topic and comparison_topic.title else 'Comparison'
problem_title = problem_topic.title if problem_topic and problem_topic.title else '??? ??'
definitions_title = definitions_topic.title if definitions_topic and definitions_topic.title else '?? ??'
relation_title = relation_topic.title if relation_topic and relation_topic.title else '??? ????'
evidence_title = evidence_topic.title if evidence_topic and evidence_topic.title else '?? ?? ??'
comparison_title = comparison_topic.title if comparison_topic and comparison_topic.title else 'DX? BIM? ??'
problem_bullets = _problem_bullets_from_raw(raw)[:2]
evidence_bullets = _evidence_bullets_from_raw(raw)[:3]
all_evidence_bullets = _evidence_bullets_from_raw(raw)
evidence_bullets = all_evidence_bullets[:2]
definition_sections = _definition_sections_from_raw(raw)[:3]
relation_bullets = _relation_bullets_from_raw(raw)[:4]
relation_bullets = _relation_bullets_from_raw(raw)[:5]
comparison_rows = _parse_comparison_rows_from_raw(raw)
preferred_axes = ['범위', '프로세스', '성과품', '확장성']
preferred_axes = ['??', '????', '???', '???']
picked_rows = [row for row in comparison_rows if row[0] in preferred_axes]
if len(picked_rows) < 4:
seen = {row[0] for row in picked_rows}
@@ -519,7 +579,7 @@ def _build_stage2_retry_html(ctx: PipelineContext, retry_plan: dict) -> dict:
seen.add(row[0])
if len(picked_rows) >= 4:
break
picked_rows = picked_rows[:3]
picked_rows = picked_rows[:4]
image_src = _extract_image_src_from_raw(raw)
if image_src and ctx.base_path:
@@ -532,26 +592,26 @@ def _build_stage2_retry_html(ctx: PipelineContext, retry_plan: dict) -> dict:
conclusion_text = _conclusion_from_raw(raw)
problem_items_html = ''.join(
f'<li style="margin-left:14px; margin-bottom:4px;">{_trim_visible_copy(item, floor=100, ceiling=220)}</li>'
f'<li style="margin-left:16px; margin-bottom:5px;">{_trim_visible_copy(item, floor=130, ceiling=280)}</li>'
for item in problem_bullets
)
evidence_items_html = ''.join(
f'<li style="margin-left:14px; margin-bottom:4px;">{_trim_visible_copy(item, floor=84, ceiling=180)}</li>'
f'<li style="margin-left:16px; margin-bottom:5px;">{_trim_visible_copy(item, floor=140, ceiling=320)}</li>'
for item in evidence_bullets
)
relation_items_html = ''.join(
f'<li style="margin-left:16px; margin-bottom:5px;">{_trim_visible_copy(item, floor=92, ceiling=210)}</li>'
f'<li style="margin-left:18px; margin-bottom:6px;">{_trim_visible_copy(item, floor=120, ceiling=260)}</li>'
for item in relation_bullets
)
definition_cards_html = ''
for idx, section in enumerate(definition_sections, start=1):
definition_cards_html += (
'<div style="background:#ffffff; border:1px solid #d7e2f0; border-radius:14px; padding:12px; display:flex; gap:10px; align-items:flex-start; min-height:96px;">'
'<div style="background:#ffffff; border:1px solid #d7e2f0; border-radius:14px; padding:12px; display:flex; gap:10px; align-items:flex-start; min-height:108px;">'
f'<div style="width:34px; height:34px; border-radius:999px; background:#2563eb; color:#fff; font-size:15px; font-weight:800; display:flex; align-items:center; justify-content:center; flex-shrink:0;">{idx}</div>'
'<div style="flex:1;">'
f'<div style="font-size:13px; font-weight:800; color:#0f172a; margin-bottom:6px; line-height:1.35;">{section["title"]}</div>'
f'<div style="font-size:9.5px; line-height:1.55; color:#334155; word-break:keep-all;">{_trim_visible_copy(section["body"], floor=150, ceiling=330)}</div>'
f'<div style="font-size:10px; line-height:1.58; color:#334155; word-break:keep-all;">{_trim_visible_copy(section["body"], floor=220, ceiling=520)}</div>'
'</div>'
'</div>'
)
@@ -560,22 +620,34 @@ def _build_stage2_retry_html(ctx: PipelineContext, retry_plan: dict) -> dict:
for axis, dx, bim in picked_rows:
comparison_rows_html += (
'<div style="display:grid; grid-template-columns:1fr 86px 1fr; border-top:1px solid #dbe5f2; align-items:stretch;">'
f'<div style="padding:6px 9px; font-size:9.4px; line-height:1.35; color:#1e3a8a; font-weight:600; background:#ffffff;">{_trim_visible_copy(dx, floor=72, ceiling=145)}</div>'
f'<div style="padding:6px 6px; font-size:9.4px; line-height:1.25; color:#1d4ed8; font-weight:800; text-align:center; background:#eff6ff; border-left:1px solid #dbe5f2; border-right:1px solid #dbe5f2; display:flex; align-items:center; justify-content:center;">{axis}</div>'
f'<div style="padding:6px 9px; font-size:9.4px; line-height:1.35; color:#475569; text-align:right; background:#ffffff;">{_trim_visible_copy(bim, floor=72, ceiling=145)}</div>'
f'<div style="padding:7px 10px; font-size:9.8px; line-height:1.42; color:#1e3a8a; font-weight:600; background:#ffffff;">{_trim_visible_copy(dx, floor=110, ceiling=220)}</div>'
f'<div style="padding:7px 6px; font-size:9.6px; line-height:1.25; color:#1d4ed8; font-weight:800; text-align:center; background:#eff6ff; border-left:1px solid #dbe5f2; border-right:1px solid #dbe5f2; display:flex; align-items:center; justify-content:center;">{axis}</div>'
f'<div style="padding:7px 10px; font-size:9.8px; line-height:1.42; color:#475569; text-align:right; background:#ffffff;">{_trim_visible_copy(bim, floor=110, ceiling=220)}</div>'
'</div>'
)
evidence_popup_html = _popup_overlay(
'popup-evidence',
evidence_title,
_popup_list_html(all_evidence_bullets, floor=220, ceiling=520),
)
comparison_popup_html = _popup_overlay(
'popup-comparison',
comparison_title,
_popup_comparison_table(comparison_rows),
)
intro_html = (
'<div style="background:linear-gradient(135deg,#fff5f5 0%,#ffe8e8 100%); border:2px solid #f8a4a4; border-radius:12px; padding:10px 14px;">'
'<div style="background:linear-gradient(135deg,#fff5f5 0%,#ffe8e8 100%); border:2px solid #f8a4a4; border-radius:12px; padding:12px 16px;">'
'<div style="display:flex; gap:12px; align-items:flex-start;">'
'<div style="font-size:24px; line-height:1; color:#f59e0b; margin-top:2px;">&#9888;</div>'
'<div style="flex:1;">'
f'<div style="font-size:12px; font-weight:900; color:#b42318; margin-bottom:5px;">{problem_title}</div>'
f'<ul style="font-size:9px; line-height:1.45; color:#7a271a; padding-left:0; margin:0 0 4px 0; list-style:disc;">{problem_items_html}</ul>'
f'<div style="font-size:8.5px; line-height:1.38; color:#9a3412; margin-top:2px;"><span style="font-weight:800;">{evidence_title}</span></div>'
f'<ul style="font-size:8.5px; line-height:1.38; color:#9a3412; padding-left:0; margin:2px 0 0 0; list-style:disc;">{evidence_items_html}</ul>'
'<div style="margin-top:8px; background:#991b1b; color:#ffffff; border-radius:4px; padding:5px 10px; font-size:10px; font-weight:800; word-break:keep-all;">&#8594; 각 용어의 정의, 역할, 상호관계에 대한 체계적 정립 필요</div>'
f'<div style="font-size:12.5px; font-weight:900; color:#b42318; margin-bottom:6px;">{problem_title}</div>'
f'<ul style="font-size:9.4px; line-height:1.5; color:#7a271a; padding-left:0; margin:0 0 6px 0; list-style:disc;">{problem_items_html}</ul>'
f'<div style="font-size:9px; line-height:1.42; color:#9a3412; margin-top:4px;"><span style="font-weight:800;">{evidence_title}</span></div>'
f'<ul style="font-size:8.9px; line-height:1.42; color:#9a3412; padding-left:0; margin:2px 0 0 0; list-style:disc;">{evidence_items_html}</ul>'
f'{_popup_button("popup-evidence", "?? ???")}'
'<div style="margin-top:8px; background:#991b1b; color:#ffffff; border-radius:4px; padding:5px 10px; font-size:10px; font-weight:800; word-break:keep-all;">&#8594; ? ??? ??, ??, ????? ?? ??? ?? ??</div>'
'</div>'
'</div>'
'</div>'
@@ -586,28 +658,31 @@ def _build_stage2_retry_html(ctx: PipelineContext, retry_plan: dict) -> dict:
f'<div style="font-size:14px; font-weight:900; color:#1f3b63; margin-bottom:6px;">{relation_title}</div>'
'<div style="display:grid; grid-template-columns:250px 1fr; gap:14px; align-items:start;">'
'<div>'
f'{_relation_visual(image_src, image_caption).replace("height:220px", "height:208px").replace("padding:10px", "padding:12px")}'
f'{_relation_visual(image_src, image_caption).replace("height:220px", "height:210px").replace("padding:10px", "padding:12px")}'
f'<div style="margin-top:8px; background:#dcfce7; border:1px solid #86efac; color:#166534; font-size:9px; line-height:1.3; border-radius:999px; padding:4px 10px; text-align:center;">{image_caption}</div>'
'</div>'
'<div style="display:flex; flex-direction:column; gap:8px;">'
f'<ul style="font-size:8.8px; line-height:1.42; color:#334155; padding-left:0; margin:0; list-style:disc;">{relation_items_html}</ul>'
f'<ul style="font-size:9px; line-height:1.46; color:#334155; padding-left:0; margin:0; list-style:disc;">{relation_items_html}</ul>'
'<div style="margin-top:4px; border:1px solid #b9d3ff; border-radius:10px; overflow:hidden;">'
'<div style="display:grid; grid-template-columns:1fr 86px 1fr; background:linear-gradient(135deg,#0d47a1,#1565c0); color:#fff; font-weight:800; font-size:12px; text-align:center;">'
'<div style="padding:7px 10px;">DX</div>'
'<div style="padding:7px 6px; background:rgba(0,0,0,0.14); font-size:11px;">\uad6c\ubd84</div>'
'<div style="padding:7px 6px; background:rgba(0,0,0,0.14); font-size:11px;">??</div>'
'<div style="padding:7px 10px;">BIM</div>'
'</div>'
f'{comparison_rows_html}'
'</div>'
f'{_popup_button("popup-comparison", "??? ?? ??")}'
'</div>'
'</div>'
'</div>'
)
body_html = (
'<div style="width:100%; height:100%; box-sizing:border-box; font-family:Segoe UI,sans-serif; color:#0f172a; display:flex; flex-direction:column; gap:6px;">'
'<div style="width:100%; height:100%; box-sizing:border-box; font-family:Segoe UI,sans-serif; color:#0f172a; display:flex; flex-direction:column; gap:8px;">'
f'{intro_html}'
f'{relation_html}'
f'{evidence_popup_html}'
f'{comparison_popup_html}'
'</div>'
)
@@ -628,7 +703,7 @@ def _build_stage2_retry_html(ctx: PipelineContext, retry_plan: dict) -> dict:
'body_html': body_html,
'sidebar_html': sidebar_html,
'footer_html': footer_html,
'reasoning': 'retry regrouping by content importance: grouped problem+evidence, reference-like relation block, comparison embedded under relation, numbered definition cards',
'reasoning': 'retry regrouping by content importance: grouped problem+evidence with popup details, relation block, visible comparison summary with full popup, numbered definition cards',
}
@@ -766,3 +841,4 @@ async def main() -> None:
if __name__ == '__main__':
asyncio.run(main())