feat(가이드): 산림청 검증 계정 캡처 · B01 산림청 장면 둘 (PLAN 558-1)

- 캡처 도구 account 에 forest(「산림청」 칸) 더함 · 지킴 시험에 forest 더함
- B01 장면 둘(메뉴 · 홈 [예정노선] 칸) 찍음 · 00 설명서 · 01 홈 글에 그림 · 번호 더함

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 10:22:46 +09:00
co-authored by Claude Sonnet 5.5
parent 2d81a57347
commit c8be1569dd
8 changed files with 85 additions and 4 deletions
+3 -3
View File
@@ -9,8 +9,8 @@
- --force = `_manual.json` 그림도 찍음 · --headed = 창을 띄워 찍음(헤드리스에서 3D · 지도가 안 그려질 때)
- 프로젝트 = A05_EduDetail/guide/_projects.json `{"road": "<id>", "stab": "<id>", "plan": "<id>"}`
· --project-id 를 주면 그 id 가 모든 프로젝트 장면에 우선(자기 검증 프로젝트로 시험할 때)
- 로그인 = 장면 칸 account 대로 — sys(기본) = tmp/aislo_cred.json · company_admin · user = tmp/verify_accounts.json
의 「회사관리자」 · 「일반」 · 계정마다 컨텍스트를 따로 로그인(쓰는 것만 · 처음 쓸 때) · 값은 어디에도 찍지 않음
- 로그인 = 장면 칸 account 대로 — sys(기본) = tmp/aislo_cred.json · company_admin · user · forest = tmp/verify_accounts.json
의 「회사관리자」 · 「일반」 · 「산림청」 · 계정마다 컨텍스트를 따로 로그인(쓰는 것만 · 처음 쓸 때) · 값은 어디에도 찍지 않음
- 끝에 표(장면 · 크기 · kB · 걸린 시간 · 실패 까닭) · 실패가 하나라도 있으면 종료 코드 1
`_scenes.json` 꼴 = 장면(그림 하나) 목록 — 이 머리 설명이 기준
@@ -18,7 +18,7 @@
{
"name": "B05_00_overview", 필수 · <페이지>_<두 자리>_<영문 소문자·숫자·_> · 그림 = <name>.webp
"route": "#/b05-profile", 필수 · 해시 주소("#/…") · vite 주소 뒤에 붙임
"account": "sys", 선택 · sys(기본 · 시스템 관리자) | company_admin | user · 역할마다 화면이 다른 페이지용
"account": "sys", 선택 · sys(기본 · 시스템 관리자) | company_admin | user | forest(산림청) · 역할마다 화면이 다른 페이지용
"project": "road", road | stab | plan | none(기본) · none = 현재 프로젝트 비움
"viewport": [1600, 900], 선택 · 화면 크기(CSS px) · 기본 1600×900 · 그림은 늘 2배
"before": [ 단계, … ], 선택 · 찍기 전 조작(아래 단계 꼴)
@@ -14,6 +14,7 @@ PROJECT_KEYS = ("road", "stab", "plan", "none")
ACCOUNT_KEYS = {
"company_admin": "회사관리자",
"user": "일반",
"forest": "산림청",
} # 장면 account → 파일 열쇠 (sys 는 CRED)
ACCOUNTS = ("sys", *ACCOUNT_KEYS)
STEP_KINDS = (
+10
View File
@@ -41,6 +41,16 @@
- 산림청 — 예정노선을 만드는 계정 · [받은 예정노선] 대신 홈에 [예정노선] 칸.
- 시스템 관리자 — 모든 메뉴 + [시스템 관리] 묶음 · 머리 줄에서 볼 회사를 바꿈.
산림청 화면 — 왼쪽 메뉴에 [받은 예정노선] · [사람] · [시스템 관리] 가 없음.
![산림청 화면](B01_00_forest_menu.webp)
| 번호 | 구역 | 하는 일 |
| --- | --- | --- |
| 1 | 사용자 메뉴 | 산림청 계정의 이름 · 내 정보 · 로그아웃 |
| 2 | 왼쪽 메뉴 | 홈 · 프로젝트 · 임시 보관함 · 회사 정보 · 회사 설계 기본값 · 사용자 가이드 · 지식 챗봇 |
| 3 | 페이지 자리 | [홈] — 오른쪽에 [예정노선] 칸 |
> 참고: 권한 없는 페이지 주소로 들어오면 [홈] 이 열림.
## 작업 순서
+4
View File
@@ -47,3 +47,7 @@
- [작게] = 한 줄 목록(9 넘으면 요약 · 그래프) · [크게] = 큰 · 중간 카드.
> 참고: 산림청 계정은 오른쪽에 [예정노선] 칸이 더 있음.
![산림청 홈](B01_01_home_forest.webp)
① [예정노선] 칸 — 분석 전 · 분석됨 · 확정 수 + 업체 지정 대기 수.
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+66
View File
@@ -54,6 +54,45 @@
}
]
},
{
"name": "B01_00_forest_menu",
"route": "#/b01-account?view=home",
"account": "forest",
"project": "none",
"viewport": [
1600,
720
],
"before": [
{
"eval": "document.head.insertAdjacentHTML('beforeend','<style>.ui-toast-container{display:none!important}</style>')"
},
{
"wait": ".b01-shell__page"
},
{
"wait_ms": 1200
},
{
"eval": "(()=>{const W=s=>s\n.replace(/홍길동\\(견본\\)/g,'이민수').replace(/엄상돈|브레인관리자/g,'홍길동').replace(/엄상준|브레인사용자/g,'김영희').replace(/박동철/g,'박철수').replace(/산림청검증/g,'홍길동')\n.replace(/캐미팩토리/g,'예시건설').replace(/브레인검증회사_\\d+/g,'예시설계').replace(/검증_[^\\s)]+/g,'가이드_견본')\n.replace(/\\d{3}-\\d{2}-\\d{5}/g,'000-00-00000').replace(/\\b0\\d{1,2}-?\\d{3,4}-?\\d{4}\\b/g,'010-0000-0000')\n.replace(/대전광역시[^\\n]*/g,'○○시 ○○구 ○○로 1').replace(/[\\w.+-]+@[\\w.-]+\\.\\w+/g,'user@example.com');\nconst fn=()=>{\n document.querySelectorAll('.b01-proj-card,.b01-proj-row').forEach(c=>{const t=c.querySelector('.b01-proj-card__name,td');if(t&&!/^가이드_/.test(t.textContent.trim()))c.style.display='none';});\n const w=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let n;\n while((n=w.nextNode())){const s=n.nodeValue;if(!s||!s.trim())continue;let v=W(s);if(/^\\s*[엄브박]\\s*$/.test(v))v='홍';if(v!==s)n.nodeValue=v;}\n document.querySelectorAll('input').forEach(i=>{const v=W(i.value);if(v!==i.value)i.value=v;});\n document.querySelectorAll('img').forEach(m=>{m.style.visibility='hidden';});};\nfn();setInterval(fn,100);})()"
}
],
"clip": null,
"callouts": [
{
"n": 1,
"sel": ".b01-shell__user"
},
{
"n": 2,
"sel": ".b01-shell__nav"
},
{
"n": 3,
"sel": ".b01-shell__page"
}
]
},
{
"name": "B01_01_home_first",
"route": "#/b01-account?view=home",
@@ -96,6 +135,33 @@
}
]
},
{
"name": "B01_01_home_forest",
"route": "#/b01-account?view=home",
"account": "forest",
"project": "none",
"before": [
{
"eval": "document.head.insertAdjacentHTML('beforeend','<style>.ui-toast-container{display:none!important}</style>')"
},
{
"wait": ".b01-shell__page"
},
{
"wait_ms": 1200
},
{
"eval": "(()=>{const W=s=>s\n.replace(/홍길동\\(견본\\)/g,'이민수').replace(/엄상돈|브레인관리자/g,'홍길동').replace(/엄상준|브레인사용자/g,'김영희').replace(/박동철/g,'박철수').replace(/산림청검증/g,'홍길동')\n.replace(/캐미팩토리/g,'예시건설').replace(/브레인검증회사_\\d+/g,'예시설계').replace(/검증_[^\\s)]+/g,'가이드_견본')\n.replace(/\\d{3}-\\d{2}-\\d{5}/g,'000-00-00000').replace(/\\b0\\d{1,2}-?\\d{3,4}-?\\d{4}\\b/g,'010-0000-0000')\n.replace(/대전광역시[^\\n]*/g,'○○시 ○○구 ○○로 1').replace(/[\\w.+-]+@[\\w.-]+\\.\\w+/g,'user@example.com');\nconst fn=()=>{\n document.querySelectorAll('.b01-proj-card,.b01-proj-row').forEach(c=>{const t=c.querySelector('.b01-proj-card__name,td');if(t&&!/^가이드_/.test(t.textContent.trim()))c.style.display='none';});\n const w=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let n;\n while((n=w.nextNode())){const s=n.nodeValue;if(!s||!s.trim())continue;let v=W(s);if(/^\\s*[엄브박]\\s*$/.test(v))v='홍';if(v!==s)n.nodeValue=v;}\n document.querySelectorAll('input').forEach(i=>{const v=W(i.value);if(v!==i.value)i.value=v;});\n document.querySelectorAll('img').forEach(m=>{m.style.visibility='hidden';});};\nfn();setInterval(fn,100);})()"
}
],
"clip": ".b01-shell__page",
"callouts": [
{
"n": 1,
"sel": ".b01-home__pane:has(h3:text-is('예정노선'))"
}
]
},
{
"name": "B01_01_home_many",
"route": "#/b01-account?view=home",
+1 -1
View File
@@ -198,7 +198,7 @@ def test_callout_and_clip_boxes():
assert G.union_clip([{"x": -50, "y": 0, "w": 2000, "h": 10}], 16, 1600, 900)["width"] == 1600
@pytest.mark.parametrize("acct", ["sys", "company_admin", "user"])
@pytest.mark.parametrize("acct", ["sys", "company_admin", "user", "forest"])
def test_account_ok(acct):
assert G.check_scene(_scene(account=acct), "B05") == []