diff --git a/README.md b/README.md
index d56b82a..aacfb95 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,12 @@
> 이 C/ATMI 시스템(2,000+본)을 Java Spring Boot로 전환하는 시나리오를 검증한다.
> 컴파일만 되는 스텁이 아니라 **상용급 TP 미들웨어에서 실제로 도는** 시스템이라는 점이 핵심이다.
+> **IR·데모용 자료** — [`docs/MIGRATION_COMPLEXITY.md`](docs/MIGRATION_COMPLEXITY.md):
+> "Forge가 얼마나 복잡한 레거시를 다룰 수 있는가"를 3축(양적 규모·자동변환 실패지점·
+> 실전 레거시성)으로 **전량 실측**해 정리. 포털에서도 실시간 확인:
+> `#arch`(호출그래프 552노드·41 모듈간 경로), `#mig`(마이그레이션 난이도 브리핑).
+> 지표는 전부 소스에서 자동 집계된다(`tools/gen_callgraph.py`, `tools/gen_metrics.py`).
+
---
## 1. 스택
diff --git a/app/ui/index.html b/app/ui/index.html
index 2d84592..365148b 100644
--- a/app/ui/index.html
+++ b/app/ui/index.html
@@ -122,6 +122,9 @@
+
@@ -34647,7 +34650,77 @@ function renderArchIn(pane, startMode){
}
const openArchTab = (startMode) => openTab('arch', '아키텍처·복잡도', pane => renderArchIn(pane, startMode));
-addGroup('시스템 조망', [{cd:'AR0001', nm:'아키텍처·복잡도', key:'architecture callgraph 복잡도 의존성', open:openArchTab}], true);
+/* ───────── 마이그레이션 난이도 브리핑 (IR용: 규모·난이도·레거시 실측) ───────── */
+function renderMigIn(pane){
+ const M = window.METRICS;
+ if (!M) { pane.innerHTML='지표 데이터 없음 (tools/gen_metrics.py 미실행)
'; return; }
+ const nf = n => (+n).toLocaleString('ko-KR');
+ const L = M.LEGACY;
+ // 거래 추적 트리 → 들여쓰기 목록
+ const treeHtml = (n, d) => {
+ const c = MODCOLOR[n.mod]||'#888';
+ const w = n.wr.length ? ` ✎ ${n.wr.join(', ')}` : '';
+ let s = ``
+ + `${d?'└─ ':''}`
+ + ``
+ + `${n.svc}${w}
`;
+ (n.children||[]).forEach(ch => s += treeHtml(ch, d+1));
+ return s;
+ };
+ pane.innerHTML = `
+ 마이그레이션 난이도 브리핑 — Enduro/X·ECPG·XA 레거시 (전량 실측)
+
아래 수치는 전부 소스코드에서 직접 집계한 값이다(추정·과장 없음). 단순 문법 치환으로 옮길 수 없는 지점의 밀도를 보여준다.
+
+ ${M.SCALE.map(s=>`
`).join('')}
+
+
+
+ ① 자동 변환이 실패하는 지점 — "왜 사람·자동화 없이 못 옮기나"
+
+ | 레거시 요소 | 실측 | Java·Spring 이전이 어려운 이유 |
+ ${M.HAZARDS.map(h=>`
+ | ${h.t} |
+ ${nf(h.n)} ${h.u} |
+ ${h.why} |
`).join('')}
+
+
+
+ ② 거래 하나의 파급 — "매입 1건이 건드리는 프로그램·테이블·트랜잭션 경계"
+
+ ${M.TRACE.map(t=>`
+
+
${t.root} 거래 체인
+
+
연쇄 프로그램
${t.stat.programs}
+
XA 브랜치(2PC)
${t.stat.xa_branches}
+
+
+
${treeHtml(t.tree,0)}
+
`).join('')}
+
+
✎ = 해당 프로그램이 INSERT/UPDATE 하는 테이블. 하나의 tpbegin 아래 여러 XA 브랜치가 서로 다른 테이블을 잠근 채 2단계 커밋으로 원자성을 보장한다 — Spring @Transactional 경계로 그대로 옮기면 교착·이중전기가 재현된다.
+
+
+ ③ 레거시 실측 인벤토리 — "만든 예제가 아니라 실전 레거시임"
+
+
+ | 가장 많이 공유되는 카피북 | ${L.copybook_shared_name} — ${nf(L.copybook_shared_max)}개 프로그램이 #include (구조체 1줄 바꾸면 ${nf(L.copybook_shared_max)}곳 파급) |
+ | 카피북 헤더 총량 | ${nf(L.copybook_total)} 개 |
+ | 전처리 매크로(#define) | ${nf(L.macro_defs)} 개 (전개 전엔 실제 로직이 안 보임) |
+ | sqlca.sqlcode 수동 에러분기 | ${nf(L.sqlca_checks)} 곳 (예외 대신 리턴코드 분기 — 전부 개별 이관 필요) |
+ | 임베디드 SQL 밀도 | 파일당 평균 ${L.embedded_sql_density} 블록 |
+ | 운영 userlog 호출 | ${nf(L.userlog)} 곳 |
+ | goto 잔존 | ${nf(L.goto)} 곳 |
+
+
+
`;
+}
+const openMigTab = () => openTab('mig', '마이그레이션 난이도', renderMigIn);
+
+addGroup('시스템 조망', [
+ {cd:'AR0001', nm:'아키텍처·복잡도', key:'architecture callgraph 복잡도 의존성', open:openArchTab},
+ {cd:'AR0002', nm:'마이그레이션 난이도', key:'migration 난이도 규모 레거시 ir', open:openMigTab},
+], true);
addGroup('조회/현황 (운영)', QSCREENS.map(q => ({cd:q.cd, nm:q.nm, key:q.cd, open:()=>openQueryTab(q)})), true);
const FAVS = [
{ svc:"ACQUIRE", nm:"매입 접수(정산체인)", note:"글로벌 XA: ACQUIRE→RECONCILE→SETTLE 체인 후 2PC 커밋" },
@@ -34700,6 +34773,7 @@ setTimeout(async () => {
if (h.startsWith('#s=')) { const s = bySvc[h.slice(3)]; if (s) openSvcTab(s); }
if (h === '#arch') openArchTab();
if (h === '#archm') openArchTab('mod');
+ if (h === '#mig') openMigTab();
}, 0);