Add remaining samples, tooling, and local project assets

This commit is contained in:
2026-04-15 18:02:17 +09:00
parent 05d43a7999
commit 1ff6c6cbb2
862 changed files with 18979 additions and 21 deletions

306
make_mdx03.py Normal file
View File

@@ -0,0 +1,306 @@
"""MDX 03: slide-base + prerequisites-3col(상단) + process-product-2col(하단) + pill(결론).
블록 CSS 글씨 크기 그대로. font-size override 금지.
텍스트 원문 그대로. 요약/추론 금지.
"""
import base64, re
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
BLOCKS = Path("templates/blocks")
SVG = BLOCKS / "svg"
OUT = Path("data/runs/mdx03_final")
OUT.mkdir(parents=True, exist_ok=True)
env = Environment(loader=FileSystemLoader(str(BLOCKS)), autoescape=False)
def b64(f):
p = SVG / f
if not p.exists(): return ""
ext = "svg+xml" if f.endswith(".svg") else "png"
return f"data:image/{ext};base64," + base64.b64encode(p.read_bytes()).decode()
def clean(h): return re.sub(r'<!--.*?-->', '', h, flags=re.DOTALL).strip()
# ══════════════════════════════════════════
# 상단: prerequisites-3col — MDX 원문 그대로
# ══════════════════════════════════════════
class _Item:
def __init__(self, heading, desc): self.heading = heading; self.desc = desc
class _Col:
def __init__(self, **kw):
for k, v in kw.items(): setattr(self, k, v)
# 블록 CSS만 가져오고, HTML은 콘텐츠 items 수에 맞게 동적 생성
# prerequisites-3col의 CSS를 추출
_p3c_raw = (BLOCKS / "new" / "prerequisites-3col.html").read_text(encoding="utf-8")
_p3c_css = re.search(r'<style>(.*?)</style>', _p3c_raw, re.DOTALL).group(0)
# MDX 원문 그대로 — 각 열의 항목 수가 다름
cols_data = [
{
"name": "기술", "sub": "디지털",
"bar": "linear-gradient(180deg, #0D78D0 0%, #023056 100%)",
"hgrad": "linear-gradient(180deg, #0D78D0 0%, #134D7F 100%)",
"items": [
("Digital 기술(S/W, H/W)과 업무 Process의 통합", [
"기존 업무 프로세스에 다양한 디지털 기술을 접목하여 업무 수행",
"프로젝트 전반에 걸친 업무 프로세스의 연결 및 조율",
]),
("분야별 전문 지식(설계, 시공, 유지관리 등) 보유", [
"건설 전 단계에 대한 근본적인 이해와 지식 및 경험",
"최신 토목 기술 트랜드 및 표준 기준 등에 대한 높은 지식",
]),
],
},
{
"name": "사람", "sub": "역량",
"bar": "linear-gradient(180deg, #FF9A23 0%, #CC5200 100%)",
"hgrad": "linear-gradient(180deg, #CC5200 0%, #883700 100%)",
"items": [
("혁신적 사고방식과 창의적 문제 해결 능력", [
"기존 수행 방식과 관습적 사고 등에 의한 접근 방식 탈피",
"디지털 기술을 활용한 창의적, 혁신적인 솔루션 제시",
]),
("사용자 중심 사고와 DX 수행 경험", [
"사용자의 요구와 기대를 충족시키는 설계 및 구현",
"시행착오를 포함한 수행 경험과 사용자 경험(UX)을 반영한 해결 방안 제시",
]),
],
},
{
"name": "자연", "sub": "여건",
"bar": "linear-gradient(180deg, #39BE49 0%, #23742C 100%)",
"hgrad": "linear-gradient(180deg, #39BE49 0%, #1E6328 100%)",
"items": [
("지속적인 투자 및 실행 의지", [
"기술 도입 초기 단계에 필요한 인력·기간·비용 등의 대규모 투자",
"기술 고도화를 위한 지속적인 개선 및 투자 체계 구축",
"변화와 혁신을 통해 부가가치를 창출하려는 실행 의지와 추진력",
]),
],
},
]
# 동적 HTML 생성 — 블록 CSS 클래스 사용, items 수 동적
cols_html = ""
for col in cols_data:
items_html = ""
n = len(col["items"])
for i, (heading, bullets) in enumerate(col["items"]):
pct_h = int(95 / n)
pct_top = int(3 + i * (95 / n))
bul = "".join(f'<div class="bul">• {b}</div>' for b in bullets)
items_html += f"""
<div class="p3c-section" style="position:absolute;left:60px;right:6px;top:{pct_top}%;height:{pct_h}%;">
<div class="p3c-heading" style="background-image:{col['hgrad']}">{heading}</div>
<div class="p3c-desc">{bul}</div>
</div>"""
if i < n - 1:
line_top = pct_top + pct_h
items_html += f'<div class="p3c-mid-line" style="position:absolute;left:56px;right:0;top:{line_top}%;border-top:1.2px dashed #000;"></div>'
cols_html += f"""
<div class="p3c-col" style="flex:1;position:relative;height:100%;border-top:1.2px solid #000;border-bottom:1.2px solid #000;">
<div class="p3c-bar" style="background:{col['bar']};position:absolute;left:0;top:0;width:56px;height:100%;"></div>
<div class="p3c-vlabel-area" style="position:absolute;left:0;top:0;width:56px;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;z-index:3;">
<div class="p3c-vlabel">{col['name']}</div>
<div class="p3c-vlabel-sub">{col['sub']}</div>
</div>
{items_html}
</div>"""
top = f'<div class="block-p3c" style="display:flex;gap:12px;width:100%;height:100%;">{cols_html}</div>\n{_p3c_css}'
# ══════════════════════════════════════════
# 하단: process-product-2col — MDX 원문 그대로
# ══════════════════════════════════════════
# process-product-2col: 블록 CSS 사용 + HTML은 좌우 소제목이 같은 행에 오도록 Grid 재구성
_pp2_raw = (BLOCKS / "BEPs" / "process-product-2col.html").read_text(encoding="utf-8")
_pp2_css = re.search(r'<style>(.*?)</style>', _pp2_raw, re.DOTALL).group(0)
arrow_uri = b64("arrow_asis_tobe.png")
# 좌우 소제목을 행 단위로 대응시킴
# 행1: Analogue 기반 업무의 Digital화 (As-is→To-be) | Copy & Paste로 인해... 품질 향상
# 행2: GIS + BIM의 연계 | Analogue 기반 도서 외 Digital 기반 정보물 추가
# 행3: 사용자 중심의 Solution 제공 | Solution을 활용한 업무 효율화
# 2열 grid + ::before로 열 배경 gradient → 행 높이 동기화 + gradient 유지
bottom = f"""
<div class="pp2-grid-wrap" style="position:relative;width:100%;height:100%;">
<!-- 좌측 배경 gradient (::before 대신 absolute div) -->
<div style="position:absolute;left:0;top:0;width:50%;height:100%;background:linear-gradient(180deg,#ffffff 46%,#39311e 100%);z-index:0;"></div>
<!-- 우측 배경 gradient -->
<div style="position:absolute;left:50%;top:0;width:50%;height:100%;background:linear-gradient(0deg,#296b55 0%,#ffffff 56%);z-index:0;"></div>
<!-- 2열 grid — 행 높이 동기화 -->
<div style="position:relative;z-index:1;display:grid;grid-template-columns:1fr 1fr;width:100%;height:100%;">
<!-- 헤더 행 -->
<div class="pp2-header-bar pp2-header-bar--left" style="background:linear-gradient(270deg,#a4a096 0%,#39311e 100%);border-radius:0 24px 24px 0;display:flex;align-items:center;justify-content:center;height:30px;margin-top:4px;">
<span class="pp2-header-text pp2-header-text--left" style="font-size:13px;font-weight:900;color:#3e3523;">과정(Process)의 혁신</span>
</div>
<div class="pp2-header-bar pp2-header-bar--right" style="background:linear-gradient(90deg,#296b55 0%,#022017 100%);border-radius:24px 0 0 24px;display:flex;align-items:center;padding-left:20px;height:30px;margin-top:4px;">
<span class="pp2-header-text pp2-header-text--right" style="font-size:13px;font-weight:900;color:#ffffff;">결과(Product)의 변화</span>
</div>
<!-- 행1: Analogue Digital화 | 품질 향상 -->
<div style="padding:3px 16px;">
<div class="pp2-mid-title pp2-mid-title--left">Analogue 기반 업무의 Digital화</div>
<div style="display:flex;align-items:center;gap:4px;">
<div style="flex:1;">
<div class="pp2-body-text">• <strong>개념·문서·행정 절차 중심</strong></div>
<div class="pp2-body-text">• <strong>2D 도면, 전문가, 규정</strong></div>
<div class="pp2-body-text">• <strong>업무 구분(단절), 책임</strong></div>
</div>
<div style="flex-shrink:0;width:30px;text-align:center;"><img src="{arrow_uri}" style="width:30px;height:16px;object-fit:contain;" alt=""></div>
<div style="flex:1;">
<div class="pp2-body-text">• <strong>시각화된 목적물, 소통, 투명성 중심</strong></div>
<div class="pp2-body-text">• <strong>3D 모델, 참여자, 실체</strong></div>
<div class="pp2-body-text">• <strong>협업(융·복합), 창의성</strong></div>
</div>
</div>
</div>
<div style="padding:3px 16px;">
<div class="pp2-mid-title pp2-mid-title--right">Copy &amp; Paste로 인해 하향 평준화된 기존 성과물의 품질 향상</div>
<div class="pp2-body-text">• 과거 수작업으로 시행하면서 발생하던 오류 등의 최소화</div>
<div class="pp2-body-text">• 정확한 Data에 기반한 계획으로 고품질 성과물 도출</div>
</div>
<!-- 행2: GIS+BIM | Digital 정보물 -->
<div style="padding:2px 16px;">
<div class="pp2-mid-title pp2-mid-title--left">GIS + BIM의 연계</div>
<div class="pp2-body-text">• 지리·지형·지반 등 위치정보(GIS)와 3D모델(형상, 속성정보) 기반의 건설 정보를 포함하는 BIM의 연계를 통한 업무 프로세스의 혁신</div>
</div>
<div style="padding:2px 16px;">
<div class="pp2-mid-title pp2-mid-title--right">Analogue 기반 도서 외 Digital 기반 정보물 추가</div>
<div class="pp2-body-text">• 기존 성과물(도면, 수량, 계산서, 시방서 등)에 3D 모델, Simulation 등의 Digital 기반 정보물 추가</div>
</div>
<!-- 행3: Solution -->
<div style="padding:2px 16px;">
<div class="pp2-mid-title pp2-mid-title--left">사용자 중심의 Solution 제공</div>
<div class="pp2-body-text">• 서로 다른 S/W로 작성되어 분절화된 Analogue 방식의 성과물과 정보물을 연계할 수 있는 설계·시공 Solution 제공</div>
</div>
<div style="padding:2px 16px;">
<div class="pp2-mid-title pp2-mid-title--right">Solution을 활용한 업무 효율화</div>
<div class="pp2-body-text">• Engn. Solution을 통해 성과물에 관한 이슈를 함께 검토·논의하는 협업 환경 조성</div>
<div class="pp2-body-text">• 건설 단계별 정보를 디지털 데이터로 축적하여, 건설 전 과정을 통합관리</div>
</div>
</div>
</div>
{_pp2_css}
"""
# ══════════════════════════════════════════
# slide-base 조립
# ══════════════════════════════════════════
title = "DX 실행 체계 구축 방안"
ft = 'DX는 필요한 요건과 체계를 갖춘 후 시행해야만 그 효과를 <em>기대할 수 있다</em>'
# font_hierarchy: key_msg=14, core=12, bg=12, sidebar=11
# zone제목=13px, 블록 heading=12px, 블록 desc=11px
font_override = """<style>
/* prerequisites-3col: font_hierarchy 적용 + 높이 채움 */
.p3c-heading { font-size: 12px !important; line-height: 1.5 !important; }
.p3c-desc { font-size: 11px !important; line-height: 1.6 !important; }
.p3c-desc .bul { padding-left: 12px; text-indent: -12px; }
.p3c-vlabel { font-size: 14px !important; }
.p3c-vlabel-sub { font-size: 12px !important; }
.p3c-kanji { font-size: 16px !important; }
.p3c-col { min-height: 0 !important; height: 100% !important; }
.block-p3c { height: 100% !important; }
/* 한자 숨김 — MDX 원문에 없음 */
.p3c-kanji { display: none !important; }
/* 색상바 내 라벨 영역을 바 전체로 */
.p3c-vlabel-area { width: 56px !important; }
/* section을 바 바로 옆으로 — 들여쓰기 2단 효과 */
.p3c-section { left: 60px !important; right: 6px !important; }
.p3c-mid-line { left: 56px !important; }
/* 자연(3번째 열): heading 1개이므로 하단 숨김 */
.p3c-col:nth-child(3) .p3c-section--top { height: 90% !important; }
.p3c-col:nth-child(3) .p3c-section--bottom { display: none !important; }
.p3c-col:nth-child(3) .p3c-mid-line { display: none !important; }
/* process-product-2col: font_hierarchy 적용 + 높이 채움 */
.pp2-header-text { font-size: 13px !important; letter-spacing: 0.8px !important; }
.pp2-mid-title { font-size: 12px !important; line-height: 1.5 !important; margin-top: 6px !important; }
.pp2-mid-title:first-child { margin-top: 0 !important; }
.pp2-body-text { font-size: 11px !important; line-height: 1.6 !important; padding-left: 12px !important; text-indent: -12px !important; }
.pp2-col { min-height: 0 !important; }
.pp2-header-bar { height: 30px !important; margin-top: 4px !important; }
.pp2-body { padding: 6px 16px !important; }
.block-pp2 { height: 100% !important; }
/* 우측 헤더 텍스트: 배경 gradient와 겹쳐서 안 보임 → 흰색 */
.pp2-header-text--right { color: #ffffff !important; }
/* 좌우 본문 영역 높이 맞춤 */
.pp2-body { display: flex !important; flex-direction: column !important; }
</style>"""
body = f"""
{font_override}
<div style="height:38%;margin-bottom:1%;padding-top:8px;">
<div style="font-weight:700;font-size:13px;color:#1a365d;margin-bottom:8px;">DX 시행을 위한 필수 요건</div>
<div style="height:calc(100% - 28px);padding:0 12px 0 24px;">{top}</div>
</div>
<div style="height:60%;margin-top:12px;">
<div style="font-weight:700;font-size:13px;color:#1a365d;margin-bottom:8px;">Process의 혁신과 Product의 변화</div>
<div style="height:calc(100% - 28px);padding:0 12px 0 24px;">{bottom}</div>
</div>
"""
sb = clean((BLOCKS / "slide-base.html").read_text(encoding="utf-8"))
r = sb.replace('{{ title|default("슬라이드") }}', title)
r = r.replace('{{ title|default("슬라이드 제목") }}', title)
r = r.replace('{% block body %}{% endblock %}', body)
pill = b64("pill_scroll.png")
r = r.replace('{% if footer_text %}', '').replace('{% if footer_pill_bg %}', '')
r = r.replace('{{ footer_pill_bg }}', pill).replace('{% else %}', '')
r = r.replace('<div class="slide-footer-bg slide-footer--css"></div>', '')
li = r.rfind('{% endif %}')
if li > 0: r = r[:li] + r[li + len('{% endif %}'):]
r = r.replace('{% endif %}', '').replace('{{ footer_text|safe }}', ft)
r = r.replace('src="svg/bg_slide_texture.png"', f'src="{b64("bg_slide_texture.png")}"')
r = r.replace('src="svg/line_divider.svg"', f'src="{b64("line_divider.svg")}"')
out = OUT / "final.html"
out.write_text(r, encoding="utf-8")
print(f"저장: {out} ({out.stat().st_size:,} bytes)")
# ══════════════════════════════════════════
# Selenium 검증
# ══════════════════════════════════════════
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
options = Options()
options.add_argument('--headless')
options.add_argument('--window-size=1400,900')
options.add_argument('--force-device-scale-factor=2')
driver = webdriver.Chrome(options=options)
driver.get(f"file:///{out.resolve()}")
time.sleep(2)
driver.save_screenshot(str(out).replace(".html", ".png"))
driver.quit()
# 텍스트 검증
c = re.sub(r'data:image/[^;]+;base64,[A-Za-z0-9+/=]+', 'I', r)
overrides = re.findall(r'font-size.*?!important', c)
j = re.findall(r'\{[%{].*?[%}]\}', c)
ts = ['기술','사람','자연','디지털','역량','여건',
'Digital 기술','혁신적 사고방식','지속적인 투자',
'Process의 혁신','Product의 변화',
'Analogue','Digital','GIS','BIM','Solution',
'기대할 수 있다']
m = [t for t in ts if t not in c]
print(f"Jinja:{len(j)} font-override:{len(overrides)} 텍스트:{len(ts)-len(m)}/{len(ts)}")
if m: print(f" MISSING: {m}")
else: print(" ALL OK")