2026-08-04 세션 — PFS 사이트맵(테스트/운영 테크트리) 신규
페페 지시: "pfs의 테스트 서버, 운영 서버 사이트 맵을 각각 만들어라. 사이드 메뉴로 두 맵을 넘나들수 있게 하고 다이어그램 형식, 테크트리의 개념이다. 각 그룹별 펼침/접기 기능 달아주고 전체 펼침, 전체 접기 기능도 추가해라. 운영서버에 반영 안된 항목은 주황색 배경으로 표시해라." + 추가 지시 "만든 항목은 pfs 기획서 링크로 연결해."
🆕 2차 지시(같은 날): "가로형 트리맵 형태로 변경해줘. 큰 제목만 표기하고 상세내용은 클릭시 팝업으로 보이기. 다이어그램형태로 PFS 제목부터 관계도로 연결해줘." → 아래 «2차 — 가로형 관계도» 절.
무엇
- 산출물 2개
- 페이지 =
devplan/pfs-sitemap.html(devplan 8799, 링크 =/pfs-sitemap.html) - 빌더 = pfs 리포
tools/sitemap/{build_sitemap.py, sitemap_template.html, dump_menu_catalog.py, menu_dump.json}(master5c2aef2. 서버 코드 무변경 — tools/ 만 추가라 테스트 서버 동작에 영향 없음) - 기획서 연결 =
devplan/pfs.html(PFS 배포 관리 = pfs 카드의 기획 페이지)에 ①탭🗺 사이트맵(테스트·운영)②상단 안내 박스 링크. 사이트맵 쪽에서도 사이드 메뉴로PFS 배포 관리로 돌아온다(양방향).
지도의 원천 — 손으로 적은 값 0
| 층 | 원천 | 수집 방법 |
|---|---|---|
| 그룹·페이지 이름·사이드바 노출 | 두 RDS menu_catalog |
dump_menu_catalog.py → ssm_run.py(EC2 경유, 운영 RDS는 로컬 직결 불가) |
| 실제 존재하는 화면 | 두 브랜치 소스 | git archive origin/master / origin/deploy-live → Blueprint·@route·render_template 파싱 |
| 화면이 쓰는 API | api/config.py add_namespace + 컨트롤러 @api.route |
템플릿·JS 에서 호출 리터럴 역추적 |
트리 = 그룹 → 페이지 → API 엔드포인트 3단. 실측 결과 그룹 8 · 페이지 49 · API 893 · 사이드바 노출 21.
운영 미반영 판정
커밋 수가 아니라 화면 뒷단 파일 내용 해시 비교(playbook §2 — 07-27 파일단위 포워드 탓 patch-id 비교가 28건으로 부풀려진 전례). 페이지의 뒷단 = 컨트롤러 + 템플릿(+모바일 분기) + 그 템플릿이 부르는 static js/css + 연결 API 모듈.
new= 운영에 라우트/메뉴 자체가 없음 ·diff= 있으나 뒷단 파일 내용이 다름 → 둘 다 주황 배경(배지로 구분)- 오늘 실측 = 2개 화면 —
AL발주서 /field/al-orderpaper·AL설정 관리 /field/al-profile(둘 다 AL 계열 QA 8건) menu_catalog는 두 서버 46행 동일(메뉴 차원 미반영 0). 테이블 수 차이 87/85 는 백업 테이블뿐.
🔑 트리에 안 걸리는 미반영 파일은 따로 열거(화면 밖 미반영 파일 절, 24건 = 공통 유틸 리팩토링·patch44 마이그·
검증 스크립트). 이걸 안 적으면 "미반영 2"가 리포 전체 미반영으로 오독된다 — 지도가 거짓말하는 지점이었다.
화면 기능
사이드 메뉴 🧪 테스트 서버 맵 / 🚀 운영 서버 맵 전환(상단 토글과 동기) · 그룹 헤더 클릭 = 그룹 접기/펼침 ·
페이지 클릭 = API 목록 접기/펼침 · 전체 펼침/전체 접기 · 미반영만 필터 · 메뉴·URL 검색.
운영 맵은 운영에 없는 화면(new)을 빼고 그리고 미반영 배지·주황을 쓰지 않는다(운영엔 미반영이라는 개념이 없다).
검증
- 빌더 자체검사
--selftest7항목 PASS(라우트 49·API 238·자산추적·네임스페이스 최장매칭·메서드 인자 건너뛰기· 중첩 별칭·주석 라벨). SSM 덤프까지 포함한 full 재빌드 E2E 1회. - devplan 게이트 =
check_theme.pyFAIL 0 ·check_font.pyFAIL 0. - 실브라우저(1512×950) — 콘솔 오류 favicon 404 뿐 · 전체펼침 후 API 행 882개 표시 · 전체접기 0 ·
주황 배경 실측
rgb(253,236,205)vs 일반rgb(250,247,242)· 맵 전환 시 KPI8/49/21/893/2↔…/0· 미반영만 = 2노드 · 검색monthly= 1노드 · 390px 가로 스크롤 0(docW==clientW==375). pfs.html에서 사이트맵 링크 2개 확인 +fetch('/pfs-sitemap.html')200.
함정 4 (다음에 또 밟는다)
- 라우트 URL 의
<int:no>가 HTML 태그로 먹힌다 —innerHTML에 그냥 넣으면/api/x/<int:no>/excel이/api/x//excel로 보인다. 트리 렌더는 전부esc()경유. render_template인자가 다음 줄에 있으면 줄 단위 파싱이 놓친다(견적 v2 3화면이 전부 그 꼴) → 함수 본문을 통째 이어붙여 정규식. 한 함수가 모바일/데스크톱 템플릿 2개를 렌더하는 것도 있다(omc) → 리스트로 받는다.- JS 의 API 호출은
apiURL()직호출이 아니다 — 화면마다 지역 래퍼(dcApi(path)/api(method, path))로 한 번 더 감싼다. 별칭을 안 따라가면 페이지 절반이 "API 0개"로 나온다. 메서드 인자('GET')를 경로로 오독하지 않게 앞의 대문자 문자열 하나는 건너뛴다. - 🔴 별칭 정규식의 본문은 lookahead 로만 봐야 한다 — 소비하면 바깥 래퍼(
function bootstrap(){ try { function api(…)가 안쪽 진짜 별칭을 통째로 먹어 한 파일에서 별칭이 하나만 잡힌다(admin JS 가 실제 그 꼴). 증상이 "이 페이지만 API 0"이라 원인이 안 보인다.
갱신 방법
cd C:\dev\pfs
python tools/sitemap/build_sitemap.py --dump # RDS 덤프부터 새로(SSM, 배포 후엔 이걸로)
python tools/sitemap/build_sitemap.py # 캐시된 덤프로 HTML만 재생성
python tools/sitemap/build_sitemap.py --selftest
안 한 것
- devplan 허브에 별도 카드 미등록 — 페페 지시가 "pfs 기획서 링크로 연결"이라 pfs 카드의 기획 페이지 하위로
붙였다. 독립 카드가 필요하면
projects.json+project_state.py등록이 추가로 필요하다. - 사이트맵 자동 갱신(워커) 없음 — 배포할 때 코디가 재빌드한다(rds-map 처럼 워처를 붙이는 건 별건).
- qahelper 오버레이 글자가 12px(전 devplan 페이지 공통) — 이 페이지가 만든 문제가 아니라 손대지 않았다.
2차 — 가로형 관계도로 교체 (같은 날, master ad705e3)
세로 아코디언(그룹 카드 안에 페이지 목록) → PFS 루트에서 오른쪽으로 뻗는 관계도로 전면 교체.
데이터·빌더 로직은 손대지 않았다 — sitemap_template.html 만 다시 썼다.
모양
PFS ─┬─ 견적서 (10) ⓘ
├─ 현장 (17) ● ⓘ ──┬─ AL발주서 (8)
│ ├─ 대화방 수집관리 (4)
│ └─ …
├─ … 8개 그룹
├─ 공용 API (11)
└─ 화면 밖 미반영 파일 (28) ●
- 노드엔 큰 제목 + 개수 배지만. 상세는 전부 팝업으로 뺐다.
- 공용 API·화면 밖 미반영 파일도 같은 뿌리에 매달았다 → 페이지 전체가 한 장의 관계도가 된다 (전엔 트리 밑에 별도 섹션 2개였다).
클릭 규약 (충돌을 피해 셋으로 나눴다)
| 대상 | 클릭 | 결과 |
|---|---|---|
| 페이지 노드 | 노드 | 상세 팝업 — URL·그룹·메뉴키·배포상태·설명·뒷단 파일·연결 API 전량 |
| 그룹 노드 | 노드 본체 | 접기/펼치기 |
| 그룹 노드 | ⓘ | 그룹 요약 팝업(페이지·사이드바·API 합계·미반영 수 + 페이지 버튼 → 상세로 이동) |
| 루트 PFS | 노드 | 서버 메타(브랜치·HEAD·uwsgi·menu_catalog·테이블·반대편 HEAD) |
🔑클릭 하나에 기능 둘을 걸 수 없다 — "클릭=팝업"과 "클릭=접기"가 충돌해서, 접기는 노드 본체·요약은 ⓘ로
분리했다. .info 핸들러에서 stopPropagation() 안 하면 팝업과 동시에 그룹이 접힌다(실측 후 추가).
커넥터 = 순수 CSS (JS 좌표계산 0)
.tm li { display:flex; align-items:center; } /* 노드가 자식 묶음의 세로 중앙에 온다 */
.tm ul ul > li::before { left:-22px; top:0; bottom:0; border-left } /* 세로 트렁크 */
.tm ul ul > li:first-child::before { top:50% } /* 위 잘라내기 */
.tm ul ul > li:last-child::before { bottom:50% } /* 아래 잘라내기 */
.tm ul ul > li::after { left:-22px; top:50%; width:22px; border-top } /* 가로 엘보 */
.tm ul ul::before { left:0; top:50%; width:22px; border-top } /* 부모→트렁크 스텁 */
align-items:center 덕에 top:50% 가 정확히 노드 중심이라 SVG·리사이즈 리스너가 필요 없다.
접기는 li.collapsed > ul { display:none } 한 줄.
🔑 기본 상태 = 전부 접힘
처음엔 전부 펼침으로 뒀더니 49개가 세로로 늘어져 뿌리(PFS)가 화면 밖으로 밀려났다 — 관계도가 아니라 긴 목록이 된다. 기본을 접힘으로 바꾸니 PFS→그룹 10개가 한 화면(다이어그램 높이 571px)에 들어온다. 「전체 펼침」은 버튼으로 남겼다.
함정
- 부트스트랩 번들은 쉘 규약상 페이지 스크립트 뒤에 로드된다 →
new bootstrap.Modal(...)을 즉시 호출하면bootstrap is not defined. 첫 클릭 때 지연 생성한다. - 모달 본문은
innerHTML이라<int:no>를esc()없이 넣으면 태그로 먹혀 URL 이 잘린다(1차와 같은 함정, 재발). 실측으로/api/estimate/<int:number>5건이 그대로 보이는 것까지 확인.
검증 (실브라우저, 콘솔 오류 0)
- 배치 = 루트 x235 < 그룹 x415 < 페이지 x628 → 가로형 확정. 커넥터 li 59개.
- 클릭 = 그룹 펼침 17 · ⓘ 는 팝업만 열고 접기 안 됨 · 팝업 내 페이지 버튼 → 상세 전환 · 페이지/루트/공용API(11)/화면밖(28) 팝업 전부 열림 · 닫힘.
- 필터 = 전체 펼침 49 / 전체 접기 0 / 미반영만 2(그룹 1) / 검색
monthly→ 월마감 1건. - 맵 전환 = 운영 맵에서 미반영 KPI 0 · 「화면 밖 미반영」 노드 사라짐 · 미반영 배너 숨김 → 테스트 복귀 시 원복.
- 390px 가로 스크롤 0(
docW==clientW==375) — 넓은 관계도는.diagram안에서만 가로 스크롤. check_theme/check_fontFAIL 0.