From 59a4dd4ebacc4f10b641367c2f8938c0e327d579 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 30 Sep 2026 17:31:17 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat(B01):=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=ED=9A=8C=EC=82=AC=20=EA=B3=A0=EB=A5=B4=EA=B8=B0=20?= =?UTF-8?q?=E2=80=94=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=EA=B0=80=20=EA=B3=A0=EB=A5=B8=20=ED=9A=8C=EC=82=AC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=B4=EA=B3=A0=20=EA=B3=A0=EC=B9=A8(29-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 시스템 관리자 화면: 「시스템 설정」을 리소스 현황 아래로 · 제목 줄 오른쪽에 회사 고르기(기본 자기 회사) - 고른 회사 = sessionStorage aislo.adminCompany · 프로젝트 · 사용자 관리 · 가입 요청 · 회사 관리 · 팀원 추가가 그 회사 것 - 카드 해시에 &company= · 회사 관리자 · 개인 화면은 그대로 · 임시 보관함은 사람 것이라 그대로 - 서버: /admin/projects · /admin/join-requests · /admin/members(POST · invite)에 company_id(_scope_company 한 곳 · 시스템 관리자만 남의 회사) · /admin/users 에 회사 거르기 - 라우터 700줄 넘어 나눔: 권한 도우미 B01_Dashboard_Scope · 회사 자산 B01_Dashboard_Router_Assets(본체가 include) - 시험 test_b01_settings_cards 보탬 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ly2oHAaTDYRjcniAmHR81z --- B01_Dashboard/B01_Dashboard_Api_Fetch.ts | 34 +++- B01_Dashboard/B01_Dashboard_Repository.py | 8 +- B01_Dashboard/B01_Dashboard_Router.py | 196 +++---------------- B01_Dashboard/B01_Dashboard_Router_Assets.py | 129 ++++++++++++ B01_Dashboard/B01_Dashboard_Scope.py | 36 ++++ B01_Dashboard/B01_Dashboard_UI_Modals.ts | 9 +- B01_Dashboard/B01_Dashboard_UI_Page.ts | 87 ++++++-- B01_Dashboard/B01_Dashboard_UI_Style.css | 18 ++ resources/tester/test_b01_settings_cards.py | 111 +++++++++-- ui_template/ui_template_locale_b3.ts | 1 + 10 files changed, 409 insertions(+), 220 deletions(-) create mode 100644 B01_Dashboard/B01_Dashboard_Router_Assets.py create mode 100644 B01_Dashboard/B01_Dashboard_Scope.py diff --git a/B01_Dashboard/B01_Dashboard_Api_Fetch.ts b/B01_Dashboard/B01_Dashboard_Api_Fetch.ts index cd122ca1..a4d41a74 100644 --- a/B01_Dashboard/B01_Dashboard_Api_Fetch.ts +++ b/B01_Dashboard/B01_Dashboard_Api_Fetch.ts @@ -360,16 +360,23 @@ export async function searchMemberCandidates(query: string): Promise { } /** 이미 가입한 사람을 회사에 붙인다 — 계정을 대신 만들지 않는다. */ -export function addCompanyMember(userId: number): Promise<{ member: Member }> { - return request("/dashboard/admin/members", { +export function addCompanyMember( + userId: number, + companyId?: number | null, +): Promise<{ member: Member }> { + return request(`/dashboard/admin/members${companyQuery(companyId)}`, { method: "POST", body: body({ user_id: userId }), }); } /** 아직 가입하지 않은 사람에게 가입 안내 메일만 보낸다. */ -export function inviteMember(email: string, name?: string | null): Promise { - return request("/dashboard/admin/members/invite", { +export function inviteMember( + email: string, + name?: string | null, + companyId?: number | null, +): Promise { + return request(`/dashboard/admin/members/invite${companyQuery(companyId)}`, { method: "POST", body: body({ email, name: name || null }), }); @@ -395,8 +402,11 @@ export function removeCompanyMember(userId: number): Promise { return request(`/dashboard/admin/members/${userId}`, { method: "DELETE" }); } -export async function fetchCompanyJoinRequests(): Promise { - const data = await request<{ requests: JoinRequest[] }>("/dashboard/admin/join-requests"); +/** companyId 는 시스템관리자가 대시보드에서 고른 회사(29-1) — 없으면 자기 회사. */ +export async function fetchCompanyJoinRequests(companyId?: number | null): Promise { + const data = await request<{ requests: JoinRequest[] }>( + `/dashboard/admin/join-requests${companyQuery(companyId)}`, + ); return data.requests; } @@ -410,8 +420,10 @@ export function processJoinRequest( }); } -export async function fetchCompanyProjects(): Promise { - const data = await request<{ projects: ProjectItem[] }>("/dashboard/admin/projects"); +export async function fetchCompanyProjects(companyId?: number | null): Promise { + const data = await request<{ projects: ProjectItem[] }>( + `/dashboard/admin/projects${companyQuery(companyId)}`, + ); return data.projects; } @@ -425,8 +437,10 @@ export async function fetchAllCompanies(): Promise { return data.companies; } -export async function fetchAllUsers(): Promise { - const data = await request<{ users: DashboardUser[] }>("/dashboard/admin/users"); +export async function fetchAllUsers(companyId?: number | null): Promise { + const data = await request<{ users: DashboardUser[] }>( + `/dashboard/admin/users${companyQuery(companyId)}`, + ); return data.users; } diff --git a/B01_Dashboard/B01_Dashboard_Repository.py b/B01_Dashboard/B01_Dashboard_Repository.py index b7fe4ac8..658c042f 100644 --- a/B01_Dashboard/B01_Dashboard_Repository.py +++ b/B01_Dashboard/B01_Dashboard_Repository.py @@ -338,15 +338,17 @@ async def soft_delete_project(project_id: str, actor_id: int, request: Any | Non return changed -async def list_all_users() -> list[dict[str, Any]]: +async def list_all_users(company_id: int | None = None) -> list[dict[str, Any]]: + where = " AND u.company_id = %s" if company_id else "" pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( - """SELECT u.id, u.email, u.name, u.position, u.department, u.phone, + f"""SELECT u.id, u.email, u.name, u.position, u.department, u.phone, u.role, u.status, u.company_id, c.name AS company_name, u.last_login FROM users u LEFT JOIN companies c ON c.id = u.company_id - WHERE u.deleted_at IS NULL ORDER BY u.created_at DESC LIMIT 200""" + WHERE u.deleted_at IS NULL{where} ORDER BY u.created_at DESC LIMIT 200""", + (company_id,) if company_id else (), ) rows = list(await cursor.fetchall()) for row in rows: diff --git a/B01_Dashboard/B01_Dashboard_Router.py b/B01_Dashboard/B01_Dashboard_Router.py index 9854c4be..9de54c5e 100644 --- a/B01_Dashboard/B01_Dashboard_Router.py +++ b/B01_Dashboard/B01_Dashboard_Router.py @@ -1,28 +1,14 @@ """B01_Dashboard 역할별 대시보드 API.""" import html -import mimetypes -import os from typing import Any -from fastapi import ( - APIRouter, - Depends, - File, - Form, - HTTPException, - Path, - Query, - Request, - Response, - UploadFile, -) +from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request, Response from pymysql.err import IntegrityError from common_util.common_util_auth import require_company, require_system_admin, verify_session from common_util.common_util_email import send_email from common_util.common_util_project_delete import hard_delete_project -from common_util.common_util_storage import read_stored_asset from config.config_system import APP_PUBLIC_BASE_URL, PROJECT_DELETE_HARD_ENABLED from .B01_Dashboard_Map import fetch_base_tile, geocode_address, search_address @@ -47,17 +33,6 @@ from .B01_Dashboard_Repository import ( update_project, update_user_profile, ) -from .B01_Dashboard_Repository_Assets import ( - ASSET_EXTENSIONS, - ASSET_KINDS, - ASSET_MAX_BYTES, - create_company_asset, - delete_company_asset, - get_company_asset, - list_company_assets, - update_company_asset, - write_company_asset_file, -) from .B01_Dashboard_Repository_Company import ( create_company, get_user_company, @@ -75,9 +50,9 @@ from .B01_Dashboard_Repository_Members import ( list_company_members, list_unassigned_users, remove_company_member, - set_company_logo, set_project_members, ) +from .B01_Dashboard_Router_Assets import router as assets_router from .B01_Dashboard_Schema import ( AddMemberRequest, AdminUpdateUserRequest, @@ -88,52 +63,19 @@ from .B01_Dashboard_Schema import ( JoinCompanyRequest, ProcessJoinRequest, UiPrefsRequest, - UpdateCompanyAssetRequest, - UpdateCompanyLogoRequest, UpdateProjectRequest, UpdateUserRequest, ) +from .B01_Dashboard_Scope import ( + _require_company_id, + _same_company, + _scope_company, + require_company_admin, +) router = APIRouter(prefix="/api/dashboard", tags=["B01_Dashboard"]) -async def require_company_admin( - session: dict[str, Any] = Depends(verify_session), -) -> dict[str, Any]: - if session["role"] not in ("ADMIN", "SYSTEM_ADMIN") and not session["is_master"]: - raise HTTPException(status_code=403, detail="회사 관리자 권한이 필요합니다.") - return session - - -def _require_company_id(session: dict[str, Any]) -> int: - company_id = session.get("company_id") - if company_id is None: - raise HTTPException(status_code=403, detail="회사 연결이 필요합니다.") - return int(company_id) - - -def _same_company(session: dict[str, Any], company_id: int | None) -> bool: - return company_id is not None and int(session.get("company_id") or 0) == int(company_id) - - -def _scope_company(session: dict[str, Any], company_id: int | None) -> int: - """회사 범위 자원(구성원·로고·서명)의 회사. 시스템관리자만 남의 회사를 지정할 수 있다.""" - if session["role"] == "SYSTEM_ADMIN" and company_id is not None: - return int(company_id) - own = _require_company_id(session) - if company_id is not None and int(company_id) != own: - raise HTTPException(status_code=403, detail="다른 회사의 자료는 볼 수 없습니다.") - return own - - -async def _company_asset(session: dict[str, Any], asset_id: int) -> dict[str, Any]: - asset = await get_company_asset(asset_id) - if not asset: - raise HTTPException(status_code=404, detail="자산을 찾을 수 없습니다.") - _scope_company(session, int(asset["company_id"])) - return asset - - async def _can_edit_project(session: dict[str, Any], project: dict[str, Any]) -> bool: if session["role"] == "SYSTEM_ADMIN": return True @@ -326,9 +268,10 @@ async def admin_member_candidates( @router.post("/admin/members") async def admin_add_member( payload: AddMemberRequest, + company_id: int | None = Query(None, gt=0), session: dict[str, Any] = Depends(require_company_admin), ): - member = await attach_company_member(_require_company_id(session), payload.user_id) + member = await attach_company_member(_scope_company(session, company_id), payload.user_id) if not member: raise HTTPException( status_code=409, detail="이미 다른 회사 소속이거나 찾을 수 없는 사용자입니다." @@ -339,10 +282,11 @@ async def admin_add_member( @router.post("/admin/members/invite") async def admin_invite_member( payload: InviteMemberRequest, + company_id: int | None = Query(None, gt=0), session: dict[str, Any] = Depends(require_company_admin), ): """아직 가입하지 않은 사람에게 안내 메일만 보낸다 — 계정을 대신 만들지 않는다.""" - company = await get_user_company(session.get("company_id")) + company = await get_user_company(_scope_company(session, company_id)) company_name = (company or {}).get("name") or "회사" # 메일 본문에 사람이 넣은 글자가 그대로 들어가면 남의 편지함에 임의 HTML 을 보낼 수 있다. safe_company = html.escape(company_name) @@ -369,8 +313,12 @@ async def admin_remove_member( @router.get("/admin/join-requests") -async def admin_join_requests(session: dict[str, Any] = Depends(require_company_admin)): - return {"status": "success", "requests": await list_join_requests(_require_company_id(session))} +async def admin_join_requests( + company_id: int | None = Query(None, gt=0), + session: dict[str, Any] = Depends(require_company_admin), +): + scoped = _scope_company(session, company_id) + return {"status": "success", "requests": await list_join_requests(scoped)} @router.patch("/admin/join-requests/{request_id}") @@ -392,10 +340,13 @@ async def admin_process_join_request( @router.get("/admin/projects") -async def admin_projects(session: dict[str, Any] = Depends(require_company_admin)): +async def admin_projects( + company_id: int | None = Query(None, gt=0), + session: dict[str, Any] = Depends(require_company_admin), +): return { "status": "success", - "projects": await list_company_projects(_require_company_id(session)), + "projects": await list_company_projects(_scope_company(session, company_id)), } @@ -470,9 +421,13 @@ async def system_create_company( @router.get("/admin/users") -async def system_users(session: dict[str, Any] = Depends(require_system_admin)): +async def system_users( + company_id: int | None = Query(None, gt=0), + session: dict[str, Any] = Depends(require_system_admin), +): + """company_id = 대시보드에서 고른 회사 사람만(29-1) · 없으면 전체.""" _ = session - return {"status": "success", "users": await list_all_users()} + return {"status": "success", "users": await list_all_users(company_id)} @router.patch("/admin/users/{user_id}/role") @@ -607,95 +562,4 @@ async def system_projects(session: dict[str, Any] = Depends(require_system_admin return {"status": "success", "projects": await list_all_projects()} -# ---- 회사 공유 도면 자산(로고·서명) — 회사 구성원 누구나 작성·수정, 시스템관리자는 전체 ---- - - -@router.get("/company/assets") -async def company_assets( - company_id: int | None = Query(None, gt=0), - session: dict[str, Any] = Depends(verify_session), -): - return { - "status": "success", - "assets": await list_company_assets(_scope_company(session, company_id)), - } - - -@router.post("/company/assets") -async def company_add_asset( - kind: str = Form(...), - label: str = Form(...), - user_id: int | None = Form(None, gt=0), - company_id: int | None = Form(None, gt=0), - file: UploadFile = File(...), - session: dict[str, Any] = Depends(verify_session), -): - kind = kind.upper() - if kind not in ASSET_KINDS: - raise HTTPException(status_code=400, detail="자산 종류는 LOGO 또는 SIGNATURE 입니다.") - label = label.strip()[:100] - if not label: - raise HTTPException(status_code=400, detail="이름을 넣어 주세요.") - suffix = os.path.splitext(file.filename or "")[1].lower() - if suffix not in ASSET_EXTENSIONS: - raise HTTPException(status_code=400, detail="png·jpg·webp·svg 그림만 올릴 수 있습니다.") - blob = await file.read() - if not blob or len(blob) > ASSET_MAX_BYTES: - raise HTTPException(status_code=400, detail="그림은 2MB 이하여야 합니다.") - scoped = _scope_company(session, company_id) - if user_id and user_id not in {m["id"] for m in await list_company_members(scoped)}: - raise HTTPException(status_code=400, detail="주인은 같은 회사 구성원이어야 합니다.") - path = write_company_asset_file(scoped, kind, suffix, blob) - asset_id = await create_company_asset( - scoped, kind, label, path, user_id, int(session["user_id"]) - ) - return {"status": "success", "asset_id": asset_id} - - -@router.put("/company/assets/{asset_id}") -async def company_update_asset( - asset_id: int, - payload: UpdateCompanyAssetRequest, - session: dict[str, Any] = Depends(verify_session), -): - await _company_asset(session, asset_id) - await update_company_asset(asset_id, payload.label.strip(), payload.user_id) - return {"status": "success"} - - -@router.delete("/company/assets/{asset_id}") -async def company_delete_asset(asset_id: int, session: dict[str, Any] = Depends(verify_session)): - await _company_asset(session, asset_id) - await delete_company_asset(asset_id) - return {"status": "success"} - - -@router.put("/company/logo") -async def company_set_logo( - payload: UpdateCompanyLogoRequest, - company_id: int | None = Query(None, gt=0), - session: dict[str, Any] = Depends(require_company_admin), -): - """회사 대표 로고를 지정·변경한다 (2026-09-02 사용자 확정). - - 프로젝트가 따로 고르지 않으면 도면은 이 로고를 쓴다. - """ - scoped = _scope_company(session, company_id) - if payload.logo_asset_id is not None: - asset = await get_company_asset(payload.logo_asset_id) - if not asset or asset["company_id"] != scoped or asset["kind"] != "LOGO": - raise HTTPException(status_code=400, detail="같은 회사의 로고 자산이어야 합니다.") - if not await set_company_logo(scoped, payload.logo_asset_id): - raise HTTPException(status_code=404, detail="회사를 찾을 수 없습니다.") - return {"status": "success"} - - -@router.get("/company/assets/{asset_id}/file") -async def company_asset_file(asset_id: int, session: dict[str, Any] = Depends(verify_session)): - """목록 미리보기용 그림. 도면에는 B07 이 같은 파일을 data URL 로 심는다.""" - asset = await _company_asset(session, asset_id) - blob = read_stored_asset(asset["file_path"]) - if blob is None: - raise HTTPException(status_code=404, detail="그림 파일이 없습니다.") - media_type = mimetypes.guess_type(asset["file_path"])[0] or "application/octet-stream" - return Response(content=blob, media_type=media_type) +router.include_router(assets_router) diff --git a/B01_Dashboard/B01_Dashboard_Router_Assets.py b/B01_Dashboard/B01_Dashboard_Router_Assets.py new file mode 100644 index 00000000..27077ffb --- /dev/null +++ b/B01_Dashboard/B01_Dashboard_Router_Assets.py @@ -0,0 +1,129 @@ +"""B01_Dashboard 회사 공유 도면 자산(로고·서명) API — 본체 라우터가 include 함.""" + +import mimetypes +import os +from typing import Any + +from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, Response, UploadFile + +from common_util.common_util_auth import verify_session +from common_util.common_util_storage import read_stored_asset + +from .B01_Dashboard_Repository_Assets import ( + ASSET_EXTENSIONS, + ASSET_KINDS, + ASSET_MAX_BYTES, + create_company_asset, + delete_company_asset, + get_company_asset, + list_company_assets, + update_company_asset, + write_company_asset_file, +) +from .B01_Dashboard_Repository_Members import list_company_members, set_company_logo +from .B01_Dashboard_Schema import UpdateCompanyAssetRequest, UpdateCompanyLogoRequest +from .B01_Dashboard_Scope import _scope_company, require_company_admin + +router = APIRouter() + + +async def _company_asset(session: dict[str, Any], asset_id: int) -> dict[str, Any]: + asset = await get_company_asset(asset_id) + if not asset: + raise HTTPException(status_code=404, detail="자산을 찾을 수 없습니다.") + _scope_company(session, int(asset["company_id"])) + return asset + + +# ---- 회사 공유 도면 자산(로고·서명) — 회사 구성원 누구나 작성·수정, 시스템관리자는 전체 ---- + + +@router.get("/company/assets") +async def company_assets( + company_id: int | None = Query(None, gt=0), + session: dict[str, Any] = Depends(verify_session), +): + return { + "status": "success", + "assets": await list_company_assets(_scope_company(session, company_id)), + } + + +@router.post("/company/assets") +async def company_add_asset( + kind: str = Form(...), + label: str = Form(...), + user_id: int | None = Form(None, gt=0), + company_id: int | None = Form(None, gt=0), + file: UploadFile = File(...), + session: dict[str, Any] = Depends(verify_session), +): + kind = kind.upper() + if kind not in ASSET_KINDS: + raise HTTPException(status_code=400, detail="자산 종류는 LOGO 또는 SIGNATURE 입니다.") + label = label.strip()[:100] + if not label: + raise HTTPException(status_code=400, detail="이름을 넣어 주세요.") + suffix = os.path.splitext(file.filename or "")[1].lower() + if suffix not in ASSET_EXTENSIONS: + raise HTTPException(status_code=400, detail="png·jpg·webp·svg 그림만 올릴 수 있습니다.") + blob = await file.read() + if not blob or len(blob) > ASSET_MAX_BYTES: + raise HTTPException(status_code=400, detail="그림은 2MB 이하여야 합니다.") + scoped = _scope_company(session, company_id) + if user_id and user_id not in {m["id"] for m in await list_company_members(scoped)}: + raise HTTPException(status_code=400, detail="주인은 같은 회사 구성원이어야 합니다.") + path = write_company_asset_file(scoped, kind, suffix, blob) + asset_id = await create_company_asset( + scoped, kind, label, path, user_id, int(session["user_id"]) + ) + return {"status": "success", "asset_id": asset_id} + + +@router.put("/company/assets/{asset_id}") +async def company_update_asset( + asset_id: int, + payload: UpdateCompanyAssetRequest, + session: dict[str, Any] = Depends(verify_session), +): + await _company_asset(session, asset_id) + await update_company_asset(asset_id, payload.label.strip(), payload.user_id) + return {"status": "success"} + + +@router.delete("/company/assets/{asset_id}") +async def company_delete_asset(asset_id: int, session: dict[str, Any] = Depends(verify_session)): + await _company_asset(session, asset_id) + await delete_company_asset(asset_id) + return {"status": "success"} + + +@router.put("/company/logo") +async def company_set_logo( + payload: UpdateCompanyLogoRequest, + company_id: int | None = Query(None, gt=0), + session: dict[str, Any] = Depends(require_company_admin), +): + """회사 대표 로고를 지정·변경한다 (2026-09-02 사용자 확정). + + 프로젝트가 따로 고르지 않으면 도면은 이 로고를 쓴다. + """ + scoped = _scope_company(session, company_id) + if payload.logo_asset_id is not None: + asset = await get_company_asset(payload.logo_asset_id) + if not asset or asset["company_id"] != scoped or asset["kind"] != "LOGO": + raise HTTPException(status_code=400, detail="같은 회사의 로고 자산이어야 합니다.") + if not await set_company_logo(scoped, payload.logo_asset_id): + raise HTTPException(status_code=404, detail="회사를 찾을 수 없습니다.") + return {"status": "success"} + + +@router.get("/company/assets/{asset_id}/file") +async def company_asset_file(asset_id: int, session: dict[str, Any] = Depends(verify_session)): + """목록 미리보기용 그림. 도면에는 B07 이 같은 파일을 data URL 로 심는다.""" + asset = await _company_asset(session, asset_id) + blob = read_stored_asset(asset["file_path"]) + if blob is None: + raise HTTPException(status_code=404, detail="그림 파일이 없습니다.") + media_type = mimetypes.guess_type(asset["file_path"])[0] or "application/octet-stream" + return Response(content=blob, media_type=media_type) diff --git a/B01_Dashboard/B01_Dashboard_Scope.py b/B01_Dashboard/B01_Dashboard_Scope.py new file mode 100644 index 00000000..fb0d9b5b --- /dev/null +++ b/B01_Dashboard/B01_Dashboard_Scope.py @@ -0,0 +1,36 @@ +"""B01_Dashboard 권한 · 회사 범위 도우미 — 라우터 둘(본체 · 자산)이 함께 씀.""" + +from typing import Any + +from fastapi import Depends, HTTPException + +from common_util.common_util_auth import verify_session + + +async def require_company_admin( + session: dict[str, Any] = Depends(verify_session), +) -> dict[str, Any]: + if session["role"] not in ("ADMIN", "SYSTEM_ADMIN") and not session["is_master"]: + raise HTTPException(status_code=403, detail="회사 관리자 권한이 필요합니다.") + return session + + +def _require_company_id(session: dict[str, Any]) -> int: + company_id = session.get("company_id") + if company_id is None: + raise HTTPException(status_code=403, detail="회사 연결이 필요합니다.") + return int(company_id) + + +def _same_company(session: dict[str, Any], company_id: int | None) -> bool: + return company_id is not None and int(session.get("company_id") or 0) == int(company_id) + + +def _scope_company(session: dict[str, Any], company_id: int | None) -> int: + """회사 범위 자원의 회사 — 시스템관리자면 인자 우선(대시보드 회사 고르기) · 남은 세션 회사.""" + if session["role"] == "SYSTEM_ADMIN" and company_id is not None: + return int(company_id) + own = _require_company_id(session) + if company_id is not None and int(company_id) != own: + raise HTTPException(status_code=403, detail="다른 회사의 자료는 볼 수 없습니다.") + return own diff --git a/B01_Dashboard/B01_Dashboard_UI_Modals.ts b/B01_Dashboard/B01_Dashboard_UI_Modals.ts index c54e6b4e..6a776638 100644 --- a/B01_Dashboard/B01_Dashboard_UI_Modals.ts +++ b/B01_Dashboard/B01_Dashboard_UI_Modals.ts @@ -584,7 +584,10 @@ export function openFindCompanyModal(): void { * (2026-09-06 사용자 확정). 계정을 대신 만들지 않는다. 아직 가입하지 않은 사람에게는 * 안내 메일만 보낸다. */ -export function openAddMemberModal(onCreated?: (member: Member) => void): void { +export function openAddMemberModal( + onCreated?: (member: Member) => void, + companyId?: number | null, +): void { const query = createInputField({ label: "이름 또는 이메일로 찾기", placeholder: "두 글자 이상", @@ -638,7 +641,7 @@ export function openAddMemberModal(onCreated?: (member: Member) => void): void { return; } query.setError(); - await inviteMember(text); + await inviteMember(text, null, companyId); showToast("안내 메일을 보냈습니다.", "success"); }, }); @@ -652,7 +655,7 @@ export function openAddMemberModal(onCreated?: (member: Member) => void): void { query.setError("등록할 사람을 먼저 고르십시오."); throw new Error("등록할 사람을 먼저 고르십시오."); } - const created = await addCompanyMember(picked.id); + const created = await addCompanyMember(picked.id, companyId); showToast(L("B01_Dashboard_Saved"), "success"); if (created?.member) onCreated?.(created.member); }); diff --git a/B01_Dashboard/B01_Dashboard_UI_Page.ts b/B01_Dashboard/B01_Dashboard_UI_Page.ts index bf73c045..243720e8 100644 --- a/B01_Dashboard/B01_Dashboard_UI_Page.ts +++ b/B01_Dashboard/B01_Dashboard_UI_Page.ts @@ -2,6 +2,7 @@ import { ROUTES } from "@config/config_frontend"; import { attachCollapsible } from "@ui/ui_template_collapsible"; import { createButton, + createSelectField, createTag, hideLoadingOverlay, showLoadingOverlay, @@ -12,8 +13,6 @@ import { createGeneralLayout } from "@ui/ui_template_general_layout"; import { navigateTo } from "../A00_Common/router"; import { fetchAllCompanies, - fetchAllJoinRequests, - fetchAllProjects, fetchAllUsers, fetchAuditLogs, fetchCompanyJoinRequests, @@ -59,6 +58,18 @@ export interface DashboardState { allJoinRequests: JoinRequest[]; auditLogs: AuditLog[]; resources: ResourceData | null; + /** 시스템 관리자가 고른 회사(29-1) — 프로젝트 · 사용자 · 가입 요청 · 회사 관리가 이 회사 것 */ + adminCompanyId: number | null; +} + +/** 고른 회사 id 자리 — M02 도 같은 키를 읽음(이름 바꾸지 않음) */ +const ADMIN_COMPANY_KEY = "aislo.adminCompany"; + +/** 고른 회사 — 저장값이 없거나 없는 회사면 자기 회사 */ +function pickAdminCompany(user: DashboardUser, companies: CompanyInfo[]): number | null { + const saved = Number(sessionStorage.getItem(ADMIN_COMPANY_KEY)); + if (companies.some((c) => c.id === saved)) return saved; + return user.company_id ?? companies[0]?.id ?? null; } export async function renderB01Dashboard(root: HTMLElement): Promise { @@ -78,6 +89,7 @@ export async function renderB01Dashboard(root: HTMLElement): Promise { allJoinRequests: [], auditLogs: [], resources: null, + adminCompanyId: null, }; await loadRoleData(state); root.innerHTML = ""; @@ -91,20 +103,22 @@ export async function renderB01Dashboard(root: HTMLElement): Promise { async function loadRoleData(state: DashboardState): Promise { if (state.user.role === "SYSTEM_ADMIN") { + // 회사 목록을 먼저 — 고른 회사를 가린 뒤 그 회사 것만 읽음(29-1) + state.allCompanies = await fetchAllCompanies().catch(() => []); + const companyId = pickAdminCompany(state.user, state.allCompanies); + state.adminCompanyId = companyId; const results = await Promise.allSettled([ - fetchAllCompanies(), - fetchAllUsers(), - fetchAllJoinRequests(), + fetchAllUsers(companyId), + fetchCompanyJoinRequests(companyId), fetchAuditLogs(), fetchSystemResources(7), - fetchAllProjects(), + fetchCompanyProjects(companyId), ]); - state.allCompanies = results[0].status === "fulfilled" ? results[0].value : []; - state.allUsers = results[1].status === "fulfilled" ? results[1].value : []; - state.allJoinRequests = results[2].status === "fulfilled" ? results[2].value : []; - state.auditLogs = results[3].status === "fulfilled" ? results[3].value : []; - state.resources = results[4].status === "fulfilled" ? results[4].value : null; - state.allProjects = results[5].status === "fulfilled" ? results[5].value : []; + state.allUsers = results[0].status === "fulfilled" ? results[0].value : []; + state.allJoinRequests = results[1].status === "fulfilled" ? results[1].value : []; + state.auditLogs = results[2].status === "fulfilled" ? results[2].value : []; + state.resources = results[3].status === "fulfilled" ? results[3].value : null; + state.allProjects = results[4].status === "fulfilled" ? results[4].value : []; results.forEach((res, idx) => { if (res.status === "rejected") { @@ -148,11 +162,16 @@ function settingCard(title: string, desc: string, go: () => void): HTMLElement { } /** 시스템 설정 — 역할로 카드 숨김 · M02 카드는 `?open=&root=` 로 진입(27-1d) */ -function buildSystemSettingsPanel(user: DashboardUser): HTMLElement { +function buildSystemSettingsPanel(user: DashboardUser, companyId: number | null): HTMLElement { const wrap = document.createElement("div"); wrap.className = "b01-dashboard__setting-grid"; + // 시스템 관리자는 고른 회사를 해시에 실음(29-1) + const company: Record = + user.role === "SYSTEM_ADMIN" && companyId ? { company: String(companyId) } : {}; const m02 = (title: string, open: string, root: string, desc: string): HTMLElement => - settingCard(title, desc, () => navigateTo(ROUTES.M02_MASTER_TEMPLATE, { open, root })); + settingCard(title, desc, () => + navigateTo(ROUTES.M02_MASTER_TEMPLATE, { open, root, ...company }), + ); if (user.role === "SYSTEM_ADMIN") { const desc = L("B01_Dashboard_PermMasterWrite"); wrap.append( @@ -181,9 +200,30 @@ function buildSystemSettingsPanel(user: DashboardUser): HTMLElement { return wrap; } +/** 회사 고르기(시스템 관리자만) — 고르면 저장 뒤 대시보드를 다시 읽음 */ +function buildCompanyPicker(state: DashboardState): HTMLElement { + const field = createSelectField({ + label: L("B01_Dashboard_PickCompany"), + options: state.allCompanies.map((c) => ({ value: String(c.id), text: c.name })), + value: String(state.adminCompanyId ?? ""), + compact: true, + onChange: (value) => { + sessionStorage.setItem(ADMIN_COMPANY_KEY, value); + window.dispatchEvent(new HashChangeEvent("hashchange")); + }, + }); + field.root.classList.add("b01-dashboard__company-pick"); + // 제목 줄 누름 = 접힘 — 고르기 누름은 올려 보내지 않음 + field.root.addEventListener("click", (event) => event.stopPropagation()); + return field.root; +} + /** 시스템 설정 컨테이너 — 모든 역할 · 한 줄 통째 · 제목 눌러 통째로 접힘 */ -function buildSystemSettingsSection(user: DashboardUser): HTMLElement { - const card = section(L("B01_Dashboard_SystemSettings"), buildSystemSettingsPanel(user), true); +function buildSystemSettingsSection(state: DashboardState): HTMLElement { + const { user } = state; + const picker = user.role === "SYSTEM_ADMIN" ? [buildCompanyPicker(state)] : []; + const panel = buildSystemSettingsPanel(user, state.adminCompanyId); + const card = section(L("B01_Dashboard_SystemSettings"), panel, true, picker); card.querySelector(".ui-card__body")?.classList.add("ui-collapsible"); card.querySelector(".b01-dashboard__section-header")?.classList.add("ui-collapsible__title"); attachCollapsible(card); @@ -196,26 +236,32 @@ function buildPage(state: DashboardState): HTMLElement { const grid = document.createElement("div"); grid.className = "b01-dashboard__grid"; - // 시스템 설정은 모든 역할 맨 위 한 줄(27-3a) - grid.append(buildSystemSettingsSection(state.user)); if (state.user.role === "SYSTEM_ADMIN") { + // 시스템 설정은 리소스 현황 아래(29-1) · 그 아래 컨테이너는 고른 회사 것 + const picked = state.allCompanies.filter((c) => c.id === state.adminCompanyId); grid.append( section(L("B01_Dashboard_Resources"), buildResourcePanel(state.resources), true), + buildSystemSettingsSection(state), section(L("B01_Dashboard_Projects"), projectTable(state.allProjects, state.user), true, [ createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }), ]), buildTempUploadSection(), section(L("B01_Dashboard_Users"), userTable(state.allUsers, state.user), true, [ - createButton({ label: "+", onClick: () => openAddMemberModal() }), + createButton({ + label: "+", + onClick: () => openAddMemberModal(undefined, state.adminCompanyId), + }), ]), section(L("B01_Dashboard_JoinRequests"), joinRequestTable(state.allJoinRequests, true), true), - section(L("B01_Dashboard_Companies"), companyTable(state.allCompanies, state.user), true, [ + section(L("B01_Dashboard_Companies"), companyTable(picked, state.user), true, [ createButton({ label: "+", onClick: () => openCreateCompanyModal() }), ]), ); } else if (state.user.role === "ADMIN") { + // 시스템 설정은 맨 위 한 줄(27-3a) grid.append( + buildSystemSettingsSection(state), section(L("B01_Dashboard_Projects"), projectTable(state.companyProjects, state.user), true, [ createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }), ]), @@ -228,6 +274,7 @@ function buildPage(state: DashboardState): HTMLElement { ); } else { grid.append( + buildSystemSettingsSection(state), section(L("B01_Dashboard_Projects"), projectTable(state.userProjects, state.user), true, [ createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }), ]), diff --git a/B01_Dashboard/B01_Dashboard_UI_Style.css b/B01_Dashboard/B01_Dashboard_UI_Style.css index 87d02e70..0e082f38 100644 --- a/B01_Dashboard/B01_Dashboard_UI_Style.css +++ b/B01_Dashboard/B01_Dashboard_UI_Style.css @@ -68,6 +68,24 @@ font-size: var(--text-caption); } +/* 회사 고르기 — 시스템 설정 제목 줄 오른쪽 · 라벨과 고르기를 한 줄로 */ +.ui-field.b01-dashboard__company-pick { + flex-direction: row; + align-items: center; + gap: var(--spacing-8); + margin: 0; +} + +/* 접히는 제목 줄 — 단추 칸을 접힘 표시 바로 왼쪽으로 */ +.ui-collapsible__title > .b01-dashboard__actions { + margin-left: auto; +} + +.b01-dashboard__company-pick .ui-field__label { + margin: 0; + white-space: nowrap; +} + .b01-dashboard__actions { display: flex; flex-wrap: wrap; diff --git a/resources/tester/test_b01_settings_cards.py b/resources/tester/test_b01_settings_cards.py index 8f80b046..b36822cc 100644 --- a/resources/tester/test_b01_settings_cards.py +++ b/resources/tester/test_b01_settings_cards.py @@ -1,7 +1,8 @@ """B01 대시보드 「시스템 설정」 컨테이너 시험(27-3) — 화면 조작은 ORCA. · 역할별 카드(제목 · 설명 · 이동 해시)를 Node 로 바로 돌림 — 가짜 DOM -· 모든 역할 맨 위 한 줄 · 통째로 접힘 · 「구조물 관리」 컨테이너 없음 +· 모든 역할 한 줄 · 통째로 접힘 · 「구조물 관리」 컨테이너 없음 +· 회사 고르기(29-1) — 시스템 관리자만 · 리소스 현황 아래 · 해시 company · 서버 인자 한 곳 """ from __future__ import annotations @@ -28,16 +29,18 @@ def _fn(src: str, name: str) -> str: return found.group(0) -def _cards(user: dict) -> list: +def _cards(user: dict, company_id: int | None = None) -> list: src = _text(PAGE) script = ( "interface DashboardUser { role: string; is_master: boolean }\n" - "const ROUTES = { M01_MASTER_DATA: 'm01-master-data', M02_MASTER_TEMPLATE: 'm02-master-template' };\n" + "const ROUTES = { M01_MASTER_DATA: 'm01-master-data'," + " M02_MASTER_TEMPLATE: 'm02-master-template' };\n" "const L = (k: string) => k;\n" "let went = '';\n" "const navigateTo = (r: string, q?: Record) =>" " { went = r + (q ? '?' + new URLSearchParams(q) : ''); };\n" - "const mk = (): any => ({ children: [] as any[], textContent: '', className: '', on: null as any," + "const mk = (): any => ({ children: [] as any[], textContent: '', className: ''," + " on: null as any," " append(...c: any[]) { this.children.push(...c); }," " addEventListener(_: string, f: any) { this.on = f; } });\n" "const document = { createElement: () => mk() };\n" @@ -45,13 +48,14 @@ def _cards(user: dict) -> list: + "\n" + _fn(src, "buildSystemSettingsPanel") + "\nconst user = JSON.parse(process.argv[1]);" - + "\nconsole.log(JSON.stringify(buildSystemSettingsPanel(user).children.map((c: any) => {" + + "\nconst panel = buildSystemSettingsPanel(user, JSON.parse(process.argv[2]));" + + "\nconsole.log(JSON.stringify(panel.children.map((c: any) => {" + " c.on(); return [c.children[0].textContent, c.children[1].textContent," + " decodeURIComponent(went)]; })));" ) result = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", - "--input-type=module-typescript", "-e", script, json.dumps(user)], + "--input-type=module-typescript", "-e", script, json.dumps(user), json.dumps(company_id)], capture_output=True, text=True, encoding="utf-8", timeout=60, ) # fmt: skip assert result.returncode == 0, result.stderr @@ -66,15 +70,19 @@ WRITE_CO = READ_CO.replace("CompanyRead", "CompanyWrite") @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") -def test_시스템_관리자_카드_다섯(): +def test_시스템_관리자_카드_다섯_고른_회사_해시(): m = "B01_Dashboard_PermMasterWrite" - assert _cards({"role": "SYSTEM_ADMIN", "is_master": False}) == [ + assert _cards({"role": "SYSTEM_ADMIN", "is_master": False}, 3) == [ ["B01_Dashboard_MasterElements", "B01_Dashboard_PermSourceRead", "m01-master-data"], - ["B01_Dashboard_CardUnitCombo", m, f"{M02}?open=일위대가&root=master"], - ["B01_Dashboard_CardStructCombo", m, f"{M02}?open=구조물조합&root=master"], - ["B01_Dashboard_CardStructOption", m, f"{M02}?open=옵션&root=master"], - ["B01_Dashboard_CardDrawing", m, f"{M02}?open=도면&root=master"], + ["B01_Dashboard_CardUnitCombo", m, f"{M02}?open=일위대가&root=master&company=3"], + ["B01_Dashboard_CardStructCombo", m, f"{M02}?open=구조물조합&root=master&company=3"], + ["B01_Dashboard_CardStructOption", m, f"{M02}?open=옵션&root=master&company=3"], + ["B01_Dashboard_CardDrawing", m, f"{M02}?open=도면&root=master&company=3"], ] + # 회사가 없으면 해시에 안 실음 + assert _cards({"role": "SYSTEM_ADMIN", "is_master": False})[1][2] == ( + f"{M02}?open=일위대가&root=master" + ) @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") @@ -83,7 +91,7 @@ def test_회사_관리자_개인_카드_둘(): ["B01_Dashboard_CardUnitCombo", WRITE_CO, f"{M02}?open=일위대가&root=company"], ["B01_Dashboard_CardStructCombo", WRITE_CO, f"{M02}?open=구조물조합&root=company"], ] - assert _cards({"role": "ADMIN", "is_master": False}) == company + assert _cards({"role": "ADMIN", "is_master": False}, 3) == company # 회사 인자 안 실음 assert _cards({"role": "USER", "is_master": True}) == company # 회사 마스터 = 회사 수정 assert _cards({"role": "USER", "is_master": False}) == [ ["B01_Dashboard_CardUnitCombo", READ_CO, f"{M02}?open=일위대가&root=personal"], @@ -91,18 +99,85 @@ def test_회사_관리자_개인_카드_둘(): ] -def test_모든_역할_맨_위_통째로_접힘(): +def test_모든_역할_한_줄_통째로_접힘(): src = _text(PAGE) build = _fn(src, "buildPage") - # 역할 갈래 앞에서 한 번 — 모든 역할 · 프로젝트 위 - assert build.index("grid.append(buildSystemSettingsSection(state.user))") < build.index( - 'if (state.user.role === "SYSTEM_ADMIN")' + # 세 갈래 모두 한 번씩 · 시스템 관리자는 리소스 현황 아래(29-1) · 프로젝트 위 + assert build.count("buildSystemSettingsSection(state)") == 3 + sys_part = build.split('} else if (state.user.role === "ADMIN")')[0] + assert ( + sys_part.index('L("B01_Dashboard_Resources")') + < sys_part.index("buildSystemSettingsSection(state)") + < sys_part.index('L("B01_Dashboard_Projects")') ) assert "StructureManage" not in src and "buildStructurePanel" not in src assert "top-row" not in src sect = _fn(src, "buildSystemSettingsSection") - assert "buildSystemSettingsPanel(user), true)" in sect # 한 줄 통째 + assert 'L("B01_Dashboard_SystemSettings"), panel, true, picker)' in sect # 한 줄 통째 assert '"ui-collapsible"' in sect and '"ui-collapsible__title"' in sect assert "attachCollapsible(card)" in sect config = _text(ROOT / "config" / "config_frontend.ts") assert re.search(r"PROTECTED_ROUTES[\s\S]*?ROUTES\.M02_MASTER_TEMPLATE,[\s\S]*?\];", config) + + +def test_회사_고르기_시스템_관리자만(): + src = _text(PAGE) + sect = _fn(src, "buildSystemSettingsSection") + assert 'user.role === "SYSTEM_ADMIN" ? [buildCompanyPicker(state)] : []' in sect + picker = _fn(src, "buildCompanyPicker") + assert "sessionStorage.setItem(ADMIN_COMPANY_KEY, value)" in picker + assert "event.stopPropagation()" in picker # 고르기 누름이 접힘으로 새지 않음 + assert 'const ADMIN_COMPANY_KEY = "aislo.adminCompany";' in src # M02 와 같은 키 + load = src.split("async function loadRoleData")[1].split("\n}\n")[0] + for call in ("fetchAllUsers(companyId)", "fetchCompanyJoinRequests(companyId)", + "fetchCompanyProjects(companyId)"): # fmt: skip + assert call in load, call + build = _fn(src, "buildPage") + assert "companyTable(picked, state.user)" in build + assert "openAddMemberModal(undefined, state.adminCompanyId)" in build + + +@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") +def test_고른_회사_저장값_검사(): + src = _text(PAGE) + script = ( + "interface DashboardUser { company_id?: number | null }\n" + "interface CompanyInfo { id: number }\n" + "const ADMIN_COMPANY_KEY = 'aislo.adminCompany';\n" + "const store: Record = {};\n" + "const sessionStorage = { getItem: (k: string) => store[k] ?? null };\n" + + _fn(src, "pickAdminCompany") + + "\nconst cs = [{ id: 1 }, { id: 3 }];" + + "\nconst out = [pickAdminCompany({ company_id: 1 }, cs)];" + + "\nstore[ADMIN_COMPANY_KEY] = '3'; out.push(pickAdminCompany({ company_id: 1 }, cs));" + + "\nstore[ADMIN_COMPANY_KEY] = '9'; out.push(pickAdminCompany({ company_id: 1 }, cs));" + + "\nout.push(pickAdminCompany({ company_id: null }, cs));" + + "\nconsole.log(JSON.stringify(out));" + ) + result = subprocess.run( + ["node", "--experimental-strip-types", "--no-warnings", + "--input-type=module-typescript", "-e", script], + capture_output=True, text=True, encoding="utf-8", timeout=60, + ) # fmt: skip + assert result.returncode == 0, result.stderr + # 기본 자기 회사 · 고른 회사 · 없는 회사면 자기 회사 · 자기 회사 없으면 첫 회사 + assert json.loads(result.stdout) == [1, 3, 1, 1] + + +def test_서버_회사_인자_한_곳(): + scope = _text(ROOT / "B01_Dashboard" / "B01_Dashboard_Scope.py") + assert 'if session["role"] == "SYSTEM_ADMIN" and company_id is not None:' in scope + router = _text(ROOT / "B01_Dashboard" / "B01_Dashboard_Router.py") + for name in ( + "admin_add_member", + "admin_invite_member", + "admin_join_requests", + "admin_projects", + ): + body = router.split(f"async def {name}(")[1].split("@router.")[0] + assert "company_id: int | None = Query(None, gt=0)" in body, name + assert "_scope_company(session, company_id)" in body, name + users = router.split("async def system_users(")[1].split("@router.")[0] + assert "require_system_admin" in users and "list_all_users(company_id)" in users + assert "router.include_router(assets_router)" in router + assert len(router.splitlines()) <= 700 diff --git a/ui_template/ui_template_locale_b3.ts b/ui_template/ui_template_locale_b3.ts index 24684799..63b1d25d 100644 --- a/ui_template/ui_template_locale_b3.ts +++ b/ui_template/ui_template_locale_b3.ts @@ -145,6 +145,7 @@ export const ui_locales_b3 = { B01_Dashboard_PermCompanyRead: ["회사 읽기", "Company read"], B01_Dashboard_PermCompanyWrite: ["회사 수정", "Company edit"], B01_Dashboard_PermPersonalWrite: ["개인 수정", "Personal edit"], + B01_Dashboard_PickCompany: ["회사", "Company"], /* --- B08·B09 빈 화면 (PLAN 7-3) --- */ B08_Quantity_Rebuilding: [ From f7f4572f3c7c2b63575c8c0b311d526b01e3aa52 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 30 Sep 2026 17:31:31 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix(M01):=20=EC=9E=90=EC=9E=AC=ED=91=9C?= =?UTF-8?q?=EC=97=90=20=EC=97=86=EB=8A=94=20=ED=92=88=EB=AA=A9=2034?= =?UTF-8?q?=EA=B1=B4=20=E2=80=94=20=EC=9E=AC=EB=A3=8C=20=EC=A4=84=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=EC=96=B4=20=EB=B9=84=EC=9B=80(40=EC=A4=84)?= =?UTF-8?q?=20=C2=B7=20=EB=B9=88=20=EA=B2=80=EC=83=89=EC=96=B4=20=3D=20?= =?UTF-8?q?=ED=9B=84=EB=B3=B4=20=EC=97=86=EC=9D=8C=20=C2=B7=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=95=20=EB=AA=A9=EB=A1=9D=20=EC=A0=88(PLAN=2029-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로직 14 파일 재료 줄 40 검색어를 빈 글로(억지 연결 뗌) · 그 줄만 고침(다른 줄 · 모양 그대로) - GC000432 메탈리밍쉘 {규격} 은 그대로 — BX 는 품목 있음 · NX 는 이미 후보 없음 - 엔진: 빈 검색어 = 자재표에 없는 품목 — candidates 빈 목록 · 계산 단가 없음 + 까닭 · 고름은 그대로 · 검사 틀림 안 셈(master_pick · master_material) · _틀.md 한 줄 - 결정 목록 맨 아래 「자재표에 없는 품목 — 사용자 결정」 34줄 - 시험 한 건 더함(test_masterdata_check) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt --- docs/raw/M01_결정목록/결정목록.md | 41 +++++++++++++++++++ resources/master_data/_틀.md | 1 + .../master_data/scripts/master_material.py | 7 +++- resources/master_data/scripts/master_pick.py | 18 ++++++-- .../로직_건설품셈_01장_배관공사.json | 2 +- .../로직_건설품셈_01장_철골공사.json | 2 +- .../로직_건설품셈_02장_가설공사.json | 2 +- .../로직_건설품셈_02장_조적공사.json | 2 +- .../로직_건설품셈_03장_터널공사.json | 2 +- .../로직_건설품셈_03장_토공사.json | 2 +- .../로직_건설품셈_05장_기초공사.json | 4 +- .../로직_건설품셈_05장_수장공사.json | 4 +- .../로직_건설품셈_06장_방수공사.json | 4 +- .../로직_건설품셈_06장_철근콘크리트공사.json | 12 +++--- .../로직_건설품셈_08장_금속공사.json | 2 +- .../로직_건설품셈_08장_지반조사.json | 10 ++--- .../로직_건설품셈_13장_플랜트설비공사.json | 24 +++++------ .../로직_산림품셈_12장_철근콘크리트.json | 8 ++-- resources/tester/test_masterdata_check.py | 26 +++++++++++- 19 files changed, 126 insertions(+), 47 deletions(-) diff --git a/docs/raw/M01_결정목록/결정목록.md b/docs/raw/M01_결정목록/결정목록.md index 3fa4c067..5cf8eee0 100644 --- a/docs/raw/M01_결정목록/결정목록.md +++ b/docs/raw/M01_결정목록/결정목록.md @@ -192,3 +192,44 @@ | Q125 | 기계설비 9-2-1 | QC001535 | 바탕만들기 Shot Blast 자재 두 줄(steel shot 0.215 · ø1㎜ 기준 0.415)은 별개 재료인가 기준 차이인가? | 별개 재료 / 기준 차이(하나만 씀) | 설계자가 고름 | | sub2_2 | | Q126 | 기계설비 11-1-3 | 없음(로직 없음) | 나사식 재료량표에 구경 32가 없는데 구경 30 값을 쓰는 게 맞나? (원문 30이 32의 오기인가) | 30 값 사용 / 오기로 보고 32로 고침 | 32일 때 30 값 | | sub2_2 | | Q127 | 기계설비 13-2-4 | QC001638 GC000893 | 강판 전기아크용접(V형 13-2-4)에 합금강 재질 입력 · 할증을 넣나? (13-2-3에는 있음) | 넣음 / 넣지 않음 | 재질 입력 · 할증 없음 | | sub2_2 | + +## 자재표에 없는 품목 — 사용자 결정 (34) + +자재표에 대응 품목이 없는 재료 줄 · 검색어를 비워 억지 연결을 뗌(계산 = 단가 없음 · 까닭 「자재표에 없는 품목」 · 품목을 골라 넣으면 그 값). 품목을 새로 넣을지 · 비슷한 품목으로 이을지 답. + +| 번호 | 로직 키 | 재료 이름 | 단위 | 원문 절 | 지금 상태 | +|---|---|---|---|---|---| +| N01 | GF000165 | 핀폴 손료 | 개 | 12-8 | 검색어 비움 | +| N02 | GF000181 | 원형거푸집(PE 10회) 스라브 및 기초 · 벽체 | 조 | 12-16 | 검색어 비움(2줄) | +| N03 | GF000202 | 탄성고무받침(150×150×15) | ㎡ | 12-30-2 | 검색어 비움 | +| N04 | GC000348 | 일체식 방수시트 | ㎡ | 토목 3-4-1 | 검색어 비움 | +| N05 | GC000432 | 메탈리밍쉘 NX | 개 | 토목 8-1-3 | 검색어 그대로 — BX 는 품목 있음 · NX 는 후보 없음(계산 = 단가 없음) | +| N06 | GC000433 | 샘플러(표준관입) | 개 | 토목 8-2-1 | 검색어 비움 | +| N07 | GC000435 | vane blade(대형) 베인 블레이드 | 개 | 토목 8-2-2 | 검색어 비움 | +| N08 | GC000447 GC000448 GC000449 | 에어해머 | 개 | 토목 8-4-2 | 검색어 비움(3줄) | +| N09 | GC000181 | P.P로프(9㎜) | m | 5-2-1 참고자료 | 검색어 비움 | +| N10 | GC000197 | 지오콤포지트 6.0㎜ | ㎡ | 5-4-1 ⑧ | 검색어 비움 | +| N11 | GC000212 | 강재거푸집 손료 | ㎡ | 6-3-2 | 검색어 비움 | +| N12 | GC000216 | 원형맨홀거푸집 ø740~ø1800 | 조 | 6-3-5 | 검색어 비움(5줄) | +| N13 | GC000027 | 손잡이기둥 | 개 | 2-7-6 참고자료 | 검색어 비움 | +| N14 | GC000127 | 각재 10㎝×10㎝ | ㎥ | 3-8-3 ④ | 검색어 비움 | +| N15 | GC000560 | 잠호용접와이어 | ㎏ | 1-1-3·1-1-4 | 검색어 비움 | +| N16 | GC000576 | 치장벽돌 | 매 | 2-1-2 | 검색어 비움 | +| N17 | GC000616 | 테라조 걸레받이 | m | 5-3-5 | 검색어 비움 | +| N18 | GC000616 | 합성수지 걸레받이 | m | 5-3-5 | 검색어 비움 | +| N19 | GC000646 | 조인트테이프 | m | 6-5-3 | 검색어 비움 | +| N20 | GC000646 | 벤토나이트방수재 시트용 | ㎡ | 6-5-3 | 검색어 비움 | +| N21 | GC000670 | AL리벳 | 개 | 8-2-2 | 검색어 비움 | +| N22 | GC000723 | 동 및 동합금용 용접봉(BCuP3) | ㎏ | 1-2-1 참고자료 | 검색어 비움 | +| N23 | GC000931 | 걸레 | ㎏ | 13-6-1 | 검색어 비움 | +| N24 | GC000932 | 걸레 | ㎏ | 13-6-2 | 검색어 비움 | +| N25 | GC000986 | 걸레 | ㎏ | 13-10-2 | 검색어 비움 | +| N26 | GC000993 | 걸레 | ㎏ | 13-11-5 | 검색어 비움 | +| N27 | GC000932 | 신주용접봉 | ㎏ | 13-6-2 | 검색어 비움 | +| N28 | GC000972 | 가루비누 | ㎏ | 13-7-22 | 검색어 비움 | +| N29 | GC000947 | 톱날 | 개 | 13-6-15 | 검색어 비움 | +| N30 | GC000947 | Bite | 개 | 13-6-15 | 검색어 비움 | +| N31 | GC000947 | Bite | 개 | 13-6-15 | 검색어 비움 | +| N32 | GC000993 | 용접면 | 개 | 13-11-5 | 검색어 비움 | +| N33 | GC000993 | 용접홀다 | 개 | 13-11-5 | 검색어 비움 | +| N34 | GC000993 | 용접앞치마 | 개 | 13-11-5 | 검색어 비움 | diff --git a/resources/master_data/_틀.md b/resources/master_data/_틀.md index 918a4e6e..e7572b3a 100644 --- a/resources/master_data/_틀.md +++ b/resources/master_data/_틀.md @@ -225,6 +225,7 @@ - 이름 조건 = 구분·상세구분·규격이 같아도 품명이 갈리는 자재품목을 가를 때만 둠(예: 파형강관 커플링밴드 vs 접속티 — 둘 다 관경만 규격에 있어 규격 조건으로는 안 갈림). 규격과 같은 방식(낱말이 **모두** 이름에 든 줄) — 대소문자·빈칸은 안 가림. - **키를 못 박음** — 재료 줄은 늘 요소 묶음 + `검색어`(「합판(내수) 12t 서울」) · 줄에 자재품목 키를 바로 적지 않음 · 재료 입력에 키 목록(`고르기`)을 두지 않음. - 찾기 = 검색어 낱말(띄어쓰기로 나눔)이 **모두** 이름 또는 규격에 든 자재품목 줄 · 요소에 `구분` · `상세구분` 이 있으면 그 안에서만 · 단위는 같은 성격까지(아래 환산). +- `검색어` 빈 글 = 자재표에 없는 품목(`docs/raw/M01_결정목록` 「자재표에 없는 품목」) — 후보 없음 · 단가 없음 · 까닭만 · 품목을 골라 넣으면 그 값 · 검사는 틀림으로 안 셈. - 자동값 차례 = 값 있는 줄 → 줄 단위와 똑같은 단위 → 검색어 안 지역 낱말(서울 등)과 맞는 줄 → 키 차례 · 그 첫째. - 고른 품목 = 계산 요청에만 실음(`_화면_계약.md` 10장 `고름`) · 로직 파일에 저장하지 않음 · 폐기 품목은 지움. - 단위 환산 = 같은 성격만 — 무게 g · kg · ton(톤 · Ton) / 길이 mm · cm · m · km / 넓이 ㎠ · ㎡ / 부피 ㎤ · ℓ · ㎥ · 단가를 줄 단위로 바꿔 계산. diff --git a/resources/master_data/scripts/master_material.py b/resources/master_data/scripts/master_material.py index 6d6237ac..569ea602 100644 --- a/resources/master_data/scripts/master_material.py +++ b/resources/master_data/scripts/master_material.py @@ -143,7 +143,10 @@ def candidates( """고르기 조건 안 자재품목 키 — 구분 · 상세구분 · 이름 · 규격 낱말이 모두 든 줄 · 파일 차례대로. `unit` 을 주면 그 단위(호표 줄 단위)와 같은 줄만 — 단위가 다르면 다른 물건. 입력 `자재지역` 이 있으면 이름에 그 지역이 든 줄만(그런 줄이 없으면 조건 안 전부). - 조건에 `검색어` 가 있으면 기본 검색어 찾기(`master_pick`).""" + 조건에 `검색어` 가 있으면 기본 검색어 찾기(`master_pick`) · + 빈 검색어 = 자재표에 없는 품목(후보 없음).""" + if mp.empty_term(cond): + return [] if cond.get("검색어"): return mp.search(master, cond, env, unit) want = [plain_spec(w) for w in str(cond.get("규격") or "").split()] @@ -180,7 +183,7 @@ def pick(master: mf.Master, cond: dict, env: dict, unit: str | None = None) -> s def check_unit(master: mf.Master, where: str, cond: dict, unit: str | None) -> list[str]: """단위 경고 — 호표 줄·품셈재료 줄 단위와 맞는 후보가 없거나 대표 줄 단위가 다름. 틀린 데이터가 아니라 좁혀야 할 조건이라 `check_pick` 과 따로 봄(`check_master 단위`).""" - if cond.get("검색어"): + if "검색어" in cond: # 빈 검색어(자재표에 없는 품목)도 이 길 — 구분 안 후보로 경고하지 않음 return mp.check_warn(master, where, cond, unit) cond = static(cond) rows = master.index.get(MARKET, {}) diff --git a/resources/master_data/scripts/master_pick.py b/resources/master_data/scripts/master_pick.py index 445af9d9..b656e47f 100644 --- a/resources/master_data/scripts/master_pick.py +++ b/resources/master_data/scripts/master_pick.py @@ -152,6 +152,8 @@ def priced(master, item: dict, ref, env: dict, index: int | None): def _priced(master, item: dict, ref, env: dict, index: int | None): unit = item.get("단위") picked = (CHOSEN.get() or {}).get(str(index)) if index is not None else None + if not picked and empty_term(ref): # 자재표에 없는 품목 — 구분 안 첫 품목으로 잇지 않음 + return None, None, NO_ITEM, None try: if picked or (isinstance(ref, dict) and ref.get("검색어")): ref = picked or pick(master, ref, env, unit) @@ -170,19 +172,29 @@ def _priced(master, item: dict, ref, env: dict, index: int | None): return price, source, why, warn +NO_ITEM = "자재표에 없는 품목 — 검색어 비움(결정 목록) · 품목을 골라 넣음" + + +def empty_term(ref) -> bool: + """검색어 칸은 있고 비었음 = 자재표에 없는 품목(`M01_결정목록` 「자재표에 없는 품목」) — + 단가 없음 · 까닭만(고름으로 고를 수 있음) · 검사는 이 줄을 틀림으로 세지 않음.""" + return isinstance(ref, dict) and "검색어" in ref and not str(ref["검색어"] or "").strip() + + # ── 검사 ─────────────────────────────────────────────────────────────── def check_zero(master, where: str, cond: dict) -> list[str]: - """검색어 후보 0 — `{입력}` 이 든 검색어는 계산 때 채워져 여기서 못 봄.""" + """검색어 후보 0 — `{입력}` 이 든 검색어는 계산 때 채워져 여기서 못 봄 · + 빈 검색어는 `empty_term`(틀림 아님).""" term = str(cond.get("검색어") or "") if not term.strip() or "{" in term or any("{" in str(v) for v in cond.values()): - return [] if term.strip() else [f"{where} · 검색어 비어 있음"] + return [] return [] if search(master, cond) else [f"{where} · 검색어 「{term}」 후보 0"] def check_warn(master, where: str, cond: dict, unit: str | None) -> list[str]: """단위 경고 줄 — 검색어 후보가 있으나 줄 단위와 같은 성격이 하나도 없음.""" term = str(cond.get("검색어") or "") - if not unit or "{" in term or "{" in str(cond.get("구분") or ""): + if not unit or not term.strip() or "{" in term or "{" in str(cond.get("구분") or ""): return [] keys = search(master, cond) rows = master.index.get(mm.MARKET, {}) diff --git a/resources/master_data/로직_건설품셈_01장_배관공사.json b/resources/master_data/로직_건설품셈_01장_배관공사.json index 82837bed..87eb8ebc 100644 --- a/resources/master_data/로직_건설품셈_01장_배관공사.json +++ b/resources/master_data/로직_건설품셈_01장_배관공사.json @@ -106,7 +106,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": ""}, "이름": "동 및 동합금용 용접봉", "규격": "BCUP3", "단위": "㎏", diff --git a/resources/master_data/로직_건설품셈_01장_철골공사.json b/resources/master_data/로직_건설품셈_01장_철골공사.json index a362f023..7b397f08 100644 --- a/resources/master_data/로직_건설품셈_01장_철골공사.json +++ b/resources/master_data/로직_건설품셈_01장_철골공사.json @@ -203,7 +203,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": ""}, "이름": "잠호용접와이어", "단위": "㎏", "수량": "만약(용접방식 == '자동용접', 환산용접길이 * 찾기(QC001190, 재료='잠호용접와이어').자동용접, 0)", diff --git a/resources/master_data/로직_건설품셈_02장_가설공사.json b/resources/master_data/로직_건설품셈_02장_가설공사.json index d3938f99..3f359b89 100644 --- a/resources/master_data/로직_건설품셈_02장_가설공사.json +++ b/resources/master_data/로직_건설품셈_02장_가설공사.json @@ -2055,7 +2055,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": "안전발판(PSP대용) 250×3000"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "동바리·비계", "검색어": ""}, "이름": "손잡이기둥", "단위": "개", "수량": "찾기(QC000063, 구분='손잡이기둥').수량 * 찾기(QC000016, 공기=공기, 재료='강관, 비계기본틀, 비계장선틀, 가새').손율 / 100", diff --git a/resources/master_data/로직_건설품셈_02장_조적공사.json b/resources/master_data/로직_건설품셈_02장_조적공사.json index 9680707c..c654975c 100644 --- a/resources/master_data/로직_건설품셈_02장_조적공사.json +++ b/resources/master_data/로직_건설품셈_02장_조적공사.json @@ -269,7 +269,7 @@ }, { "종류": "재료", - "요소": {"구분": "콘크리트제품", "상세구분": "경계석·블록", "검색어": "주차라인블록 1000X300X150"}, + "요소": {"구분": "콘크리트제품", "상세구분": "경계석·블록", "검색어": ""}, "이름": "치장벽돌", "단위": "매", "수량": "찾기(QC001213, 벽두께=벽두께).'벽돌(19×9×5.7㎝)'", diff --git a/resources/master_data/로직_건설품셈_03장_터널공사.json b/resources/master_data/로직_건설품셈_03장_터널공사.json index 3f86a3a9..6ef88797 100644 --- a/resources/master_data/로직_건설품셈_03장_터널공사.json +++ b/resources/master_data/로직_건설품셈_03장_터널공사.json @@ -243,7 +243,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "방수석고보드 12.5t"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": ""}, "이름": "일체식 방수시트", "단위": "㎡", "수량": "찾기(QC000635, 구분='일체식 방수시트').수량", diff --git a/resources/master_data/로직_건설품셈_03장_토공사.json b/resources/master_data/로직_건설품셈_03장_토공사.json index 717c28a2..59dff7d1 100644 --- a/resources/master_data/로직_건설품셈_03장_토공사.json +++ b/resources/master_data/로직_건설품셈_03장_토공사.json @@ -3303,7 +3303,7 @@ }, { "종류": "재료", - "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": "외송각재 30×30×3600mm 서울"}, + "요소": {"구분": "목재", "상세구분": "각재·판재", "검색어": ""}, "이름": "각재 10㎝×10㎝", "단위": "㎥", "수량": "찾기(QC000245).각재", diff --git a/resources/master_data/로직_건설품셈_05장_기초공사.json b/resources/master_data/로직_건설품셈_05장_기초공사.json index dd1c3db6..00386cdc 100644 --- a/resources/master_data/로직_건설품셈_05장_기초공사.json +++ b/resources/master_data/로직_건설품셈_05장_기초공사.json @@ -2394,7 +2394,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "검색어": "와이어로프(철심) 9㎜"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "와이어로프·클램프", "검색어": ""}, "이름": "P.P로프(9㎜)", "단위": "m", "수량": "찾기(QC000334, 구분='육상부설').'P.P로프(9㎜)' / 100", @@ -5339,7 +5339,7 @@ }, { "종류": "재료", - "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": "직포 옥상방수용직포"}, + "요소": {"구분": "매트·토목섬유", "상세구분": "부직포·토목섬유", "검색어": ""}, "이름": "지오콤포지트", "단위": "㎡", "수량": "만약(재료 == '지오콤포지트', 찾기(QC000399, 구분='지오콤포지트').수량, 0)", diff --git a/resources/master_data/로직_건설품셈_05장_수장공사.json b/resources/master_data/로직_건설품셈_05장_수장공사.json index b9ab9dc3..bbee04d5 100644 --- a/resources/master_data/로직_건설품셈_05장_수장공사.json +++ b/resources/master_data/로직_건설품셈_05장_수장공사.json @@ -957,7 +957,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "처마후레싱 C/S"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": ""}, "이름": "테라조", "단위": "m", "수량": "만약(구분 == '석재류', 찾기(QC001266, 구분=구분).테라조, 0)", @@ -966,7 +966,7 @@ }, { "종류": "재료", - "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": "처마후레싱 C/S"}, + "요소": {"구분": "건축마감", "상세구분": "천장·바닥·마감재", "검색어": ""}, "이름": "합성수지 걸레받이", "단위": "m", "수량": "만약(구분 == '합성수지류', 찾기(QC001266, 구분=구분).합성수지, 0)", diff --git a/resources/master_data/로직_건설품셈_06장_방수공사.json b/resources/master_data/로직_건설품셈_06장_방수공사.json index 136c52b1..809ff6f4 100644 --- a/resources/master_data/로직_건설품셈_06장_방수공사.json +++ b/resources/master_data/로직_건설품셈_06장_방수공사.json @@ -958,7 +958,7 @@ }, { "종류": "재료", - "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": "방수석고보드 12.5t"}, + "요소": {"구분": "도료·방수·접착", "상세구분": "방수·실링", "검색어": ""}, "이름": "벤토나이트방수재", "단위": "㎡", "수량": "만약(종류 == '벤토나이트 시트', 찾기(QC001307, 구분='벤토나이트방수재', 종류='시트', 부위=부위).수량, 0)", @@ -1021,7 +1021,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "PVC코팅철선 #8"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": ""}, "이름": "조인트테이프", "단위": "m", "수량": "만약(종류 == '벤토나이트 시트', 찾기(QC001307, 구분='조인트테이프', 종류='시트', 부위=부위).수량, 0)", diff --git a/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json b/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json index ff88092d..247746d2 100644 --- a/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json +++ b/resources/master_data/로직_건설품셈_06장_철근콘크리트공사.json @@ -1272,7 +1272,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "패널(K-SPAN PANEL) 0.6×305mm"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""}, "이름": "강재거푸집 손료", "단위": "㎡", "수량": "(1 - 10 / 100) / 사용횟수", @@ -1583,7 +1583,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""}, "이름": "원형맨홀거푸집 ø740 손료", "단위": "조", "수량": "만약(규격 == 'ø740', 1 / 10, 0)", @@ -1592,7 +1592,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""}, "이름": "원형맨홀거푸집 ø900 손료", "단위": "조", "수량": "만약(규격 == 'ø900', 1 / 10, 0)", @@ -1601,7 +1601,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""}, "이름": "원형맨홀거푸집 ø1200 손료", "단위": "조", "수량": "만약(규격 == 'ø1200', 1 / 10, 0)", @@ -1610,7 +1610,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""}, "이름": "원형맨홀거푸집 ø1500 손료", "단위": "조", "수량": "만약(규격 == 'ø1500', 1 / 10, 0)", @@ -1619,7 +1619,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""}, "이름": "원형맨홀거푸집 ø1800 손료", "단위": "조", "수량": "만약(규격 == 'ø1800', 1 / 10, 0)", diff --git a/resources/master_data/로직_건설품셈_08장_금속공사.json b/resources/master_data/로직_건설품셈_08장_금속공사.json index ce0d4008..99c6e305 100644 --- a/resources/master_data/로직_건설품셈_08장_금속공사.json +++ b/resources/master_data/로직_건설품셈_08장_금속공사.json @@ -447,7 +447,7 @@ }, { "종류": "재료", - "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "검색어": "작은나사(스크류) ø 4.2㎜"}, + "요소": {"구분": "볼트·앵커·철물", "상세구분": "볼트·너트", "검색어": ""}, "이름": "AL리벳", "단위": "개", "수량": "찾기(QC001339, 구분='AL리벳', 규격='ø 4.2㎜').수량", diff --git a/resources/master_data/로직_건설품셈_08장_지반조사.json b/resources/master_data/로직_건설품셈_08장_지반조사.json index 99d23cb6..df32d621 100644 --- a/resources/master_data/로직_건설품셈_08장_지반조사.json +++ b/resources/master_data/로직_건설품셈_08장_지반조사.json @@ -346,7 +346,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": ""}, "이름": "샘플러", "단위": "개", "수량": "찾기(QC000779, 종별='샘플러').수량", @@ -522,7 +522,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": ""}, "이름": "베인 블레이드", "단위": "개", "수량": "찾기(QC000780, 종별='재료비', 세목='vane blade(대형)').수량", @@ -1545,7 +1545,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": ""}, "이름": "에어해머", "단위": "개", "수량": "찾기(QC000794, 종별='에어해머').수량", @@ -1667,7 +1667,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": ""}, "이름": "에어해머", "단위": "개", "수량": "찾기(QC000795, 종별='에어해머').수량", @@ -1789,7 +1789,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": ""}, "이름": "에어해머", "단위": "개", "수량": "찾기(QC000796, 종별='에어해머').수량", diff --git a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json index 9a6e05ba..e762b711 100644 --- a/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json +++ b/resources/master_data/로직_건설품셈_13장_플랜트설비공사.json @@ -6918,7 +6918,7 @@ { "종류": "재료", "요소": { "구분": "잡재료", "상세구분": "소모품", "검색어": "여과기 14\"×14〃" }, "이름": "여과기", "단위": "Sh", "수량": "3", "비목": "재료비", "출처": "건설품셈 기계설비 13-6-1" }, { "종류": "재료", - "요소": { "구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN" }, + "요소": { "구분": "잡재료", "상세구분": "소모품", "검색어": "" }, "이름": "걸레", "단위": "㎏", "수량": "2.5", @@ -7200,7 +7200,7 @@ { "종류": "재료", "요소": { "구분": "잡재료", "상세구분": "용접·가스", "검색어": "용접봉 산소" }, "이름": "가스용접봉", "단위": "㎏", "수량": "0.146", "비목": "재료비", "출처": "건설품셈 기계설비 13-6-2" }, { "종류": "재료", - "요소": { "구분": "잡재료", "상세구분": "용접·가스", "검색어": "아세틸렌 98%" }, + "요소": { "구분": "잡재료", "상세구분": "용접·가스", "검색어": "" }, "이름": "신주용접봉", "단위": "㎏", "수량": "0.073", @@ -7220,7 +7220,7 @@ { "종류": "재료", "요소": { "구분": "잡재료", "상세구분": "소모품", "검색어": "소창직" }, "이름": "소창직", "단위": "m", "수량": "0.134", "비목": "재료비", "출처": "건설품셈 기계설비 13-6-2" }, { "종류": "재료", - "요소": { "구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN" }, + "요소": { "구분": "잡재료", "상세구분": "소모품", "검색어": "" }, "이름": "걸레", "단위": "㎏", "수량": "0.73", @@ -10333,7 +10333,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": ""}, "이름": "Bite", "단위": "개", "수량": "2.5", @@ -10342,7 +10342,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "빗자루 도로청소용"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": ""}, "이름": "톱날", "단위": "개", "수량": "2.5", @@ -10351,7 +10351,7 @@ }, { "종류": "재료", - "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": "치즐(0.2㎥)"}, + "요소": {"구분": "기계·설비", "상세구분": "기계류", "검색어": ""}, "이름": "Bite", "단위": "개", "수량": "3.2", @@ -13159,7 +13159,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": ""}, "이름": "가루비누", "단위": "㎏", "수량": "0.04", @@ -15685,7 +15685,7 @@ "출처": "건설품셈 기계설비 13-10-2", "비고": "원문 규격 ø4㎜×ℓ350" }, - { "종류": "재료", "요소": { "구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN" }, "이름": "걸레", "단위": "㎏", "수량": "2", "비목": "재료비", "출처": "건설품셈 기계설비 13-10-2" }, + { "종류": "재료", "요소": { "구분": "잡재료", "상세구분": "소모품", "검색어": "" }, "이름": "걸레", "단위": "㎏", "수량": "2", "비목": "재료비", "출처": "건설품셈 기계설비 13-10-2" }, { "종류": "재료", "요소": { "구분": "잡재료", "상세구분": "소모품", "대표": "MT030665" }, "이름": "세유", "단위": "ℓ", "수량": "2", "비목": "재료비", "출처": "건설품셈 기계설비 13-10-2" }, { "종류": "재료", "요소": { "구분": "잡재료", "상세구분": "소모품", "대표": "MT030646" }, "이름": "Grease", "단위": "㎏", "수량": "0.2", "비목": "재료비", "출처": "건설품셈 기계설비 13-10-2" }, { @@ -16505,7 +16505,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": "그리스(일반범용) CROWN"}, + "요소": {"구분": "잡재료", "상세구분": "소모품", "검색어": ""}, "이름": "걸레", "단위": "㎏", "수량": "(만약(공사 == '기기', 0.73, 만약(공사 == '철골', 0.1, 만약(공사 == '배관', 0.2, 만약(공사 == 'Belt & Conveyor', 0.3, 만약(공사 == 'Heater & Tank', 0.1, 만약(공사 == 'Pump & Fan', 0.73, 만약(공사 == 'Crane 류', 0.73, 0))))))))", @@ -16649,7 +16649,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "팁(날) 노면파쇄기용"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": ""}, "이름": "용접면", "단위": "개", "수량": "(만약(공사 == '기기', 0.01, 만약(공사 == '철골', 0.02, 만약(공사 == '배관', 0.02, 만약(공사 == 'Belt & Conveyor', 0.02, 만약(공사 == 'Heater & Tank', 0.02, 만약(공사 == 'Pump & Fan', 0.01, 만약(공사 == 'Crane 류', 0.02, 0))))))))", @@ -16658,7 +16658,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "팁(날) 노면파쇄기용"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": ""}, "이름": "용접홀다", "단위": "개", "수량": "(만약(공사 == '기기', 0.01, 만약(공사 == '철골', 0.02, 만약(공사 == '배관', 0.02, 만약(공사 == 'Belt & Conveyor', 0.02, 만약(공사 == 'Heater & Tank', 0.02, 만약(공사 == 'Pump & Fan', 0.01, 만약(공사 == 'Crane 류', 0.02, 0))))))))", @@ -16667,7 +16667,7 @@ }, { "종류": "재료", - "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": "팁(날) 노면파쇄기용"}, + "요소": {"구분": "잡재료", "상세구분": "용접·가스", "검색어": ""}, "이름": "용접앞치마", "단위": "개", "수량": "(만약(공사 == '기기', 0.01, 만약(공사 == '철골', 0.05, 만약(공사 == '배관', 0.02, 만약(공사 == 'Belt & Conveyor', 0.05, 만약(공사 == 'Heater & Tank', 0.05, 만약(공사 == 'Pump & Fan', 0.01, 만약(공사 == 'Crane 류', 0.05, 0))))))))", diff --git a/resources/master_data/로직_산림품셈_12장_철근콘크리트.json b/resources/master_data/로직_산림품셈_12장_철근콘크리트.json index 64521131..9c6967b1 100644 --- a/resources/master_data/로직_산림품셈_12장_철근콘크리트.json +++ b/resources/master_data/로직_산림품셈_12장_철근콘크리트.json @@ -944,7 +944,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "비탈형영구거푸집(KC-FORM) 450용"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""}, "이름": "핀폴 손료", "단위": "개", "수량": "1 * (1 - 10 / 100) / 20", @@ -3198,7 +3198,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""}, "이름": "원형거푸집(PE 10회) 스라브 및 기초", "단위": "조", "수량": "1 / 10", @@ -3208,7 +3208,7 @@ }, { "종류": "재료", - "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": "폼타이 D형"}, + "요소": {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""}, "이름": "원형거푸집(PE 10회) 벽체", "단위": "조", "수량": "1 / 10", @@ -4656,7 +4656,7 @@ }, { "종류": "재료", - "요소": {"구분": "교량·구조부속", "상세구분": "신축이음·받침", "검색어": "익스팬션조인트휠러 t=10mm"}, + "요소": {"구분": "교량·구조부속", "상세구분": "신축이음·받침", "검색어": ""}, "이름": "탄성고무받침(150×150×15)", "단위": "㎡", "수량": "고무받침면적", diff --git a/resources/tester/test_masterdata_check.py b/resources/tester/test_masterdata_check.py index 9b57f371..5eb01763 100644 --- a/resources/tester/test_masterdata_check.py +++ b/resources/tester/test_masterdata_check.py @@ -770,8 +770,18 @@ def test_품셈재료_자재지역_후보(whole): "그룹": "재료", "원문": "품셈재료", "줄": [ - {"키": "MP900003", "이름": "모래", "단위": "㎥", "연결": {"이름": "모래", "규격": "", "지역": "입력"}}, - {"키": "MP900004", "이름": "레미콘", "단위": "㎥", "연결": {"이름": "레미콘", "규격": "", "지역": "입력"}}, + { + "키": "MP900003", + "이름": "모래", + "단위": "㎥", + "연결": {"이름": "모래", "규격": "", "지역": "입력"}, + }, + { + "키": "MP900004", + "이름": "레미콘", + "단위": "㎥", + "연결": {"이름": "레미콘", "규격": "", "지역": "입력"}, + }, ], } } @@ -1168,3 +1178,15 @@ def test_변수_참조_줄은_고르기_조건으로_바뀜(whole): and "{" in item["요소"] ] assert sorted(남음) == [] + + +def test_빈_검색어는_자재표에_없는_품목_단가_없음_검사_통과(): + """PLAN 29-3 — 빈 검색어 = 자재표에 없는 품목: 구분 안 첫 품목으로 안 이음 · + 계산은 까닭만 · 틀림 아님.""" + whole = cm.master() + cond = {"구분": "거푸집·가설재", "상세구분": "거푸집", "검색어": ""} + assert mm.candidates(whole, cond) == [] + assert mm.check_pick(whole, "시험", cond) == [] + got = mf.run(whole, "GF000165", cm.시험입력(whole.logic("GF000165"))) + line = next(x for x in got["줄"] if x["이름"] == "핀폴 손료") + assert line["단가"] is None and "자재표에 없는 품목" in line["까닭"] From e7d8ea80020d0d0246596edfba44fba4b1194309 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 30 Sep 2026 17:35:59 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat(B01):=20=ED=9A=8C=EC=82=AC=20=EA=B3=A0?= =?UTF-8?q?=EB=A5=B4=EA=B8=B0=EB=A5=BC=20=EC=95=8C=EC=95=BD=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=E2=80=94=20=ED=9A=8C=EC=82=AC=20=EB=94=B0=EB=9D=BC?= =?UTF-8?q?=20=EB=B0=94=EB=80=8C=EB=8A=94=20=EC=BB=A8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=84=88=EB=A7=88=EB=8B=A4(29-1=20=EA=B3=A0=EC=B9=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 고르기 select 뺌 · 「시스템 설정」 · 프로젝트 · 사용자 관리 · 가입 요청 · 회사 관리 제목 오른쪽에 고른 회사 알약(공용 ui-tag 모양) - 누르면 등록된 회사 작은 메뉴(공용 createMapContextMenu) · 어디서 골라도 함께 바뀜 · 알약 누름은 접힘으로 안 샘 - 회사 관리자 · 개인 화면은 알약 없음 · 임시 보관함 그대로 - 시험 test_b01_settings_cards 알약 판정으로 고침 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ly2oHAaTDYRjcniAmHR81z --- B01_Dashboard/B01_Dashboard_UI_Page.ts | 64 ++++++++++++++------- B01_Dashboard/B01_Dashboard_UI_Style.css | 47 +++++++++++++-- resources/tester/test_b01_settings_cards.py | 18 ++++-- ui_template/ui_template_locale_b3.ts | 2 +- 4 files changed, 98 insertions(+), 33 deletions(-) diff --git a/B01_Dashboard/B01_Dashboard_UI_Page.ts b/B01_Dashboard/B01_Dashboard_UI_Page.ts index 243720e8..3d0c9971 100644 --- a/B01_Dashboard/B01_Dashboard_UI_Page.ts +++ b/B01_Dashboard/B01_Dashboard_UI_Page.ts @@ -2,12 +2,12 @@ import { ROUTES } from "@config/config_frontend"; import { attachCollapsible } from "@ui/ui_template_collapsible"; import { createButton, - createSelectField, createTag, hideLoadingOverlay, showLoadingOverlay, showToast, } from "@ui/ui_template_elements"; +import { createMapContextMenu, type MapContextMenu } from "@ui/ui_template_context_menu"; import { section } from "@ui/ui_template_general_blocks"; import { createGeneralLayout } from "@ui/ui_template_general_layout"; import { navigateTo } from "../A00_Common/router"; @@ -200,30 +200,46 @@ function buildSystemSettingsPanel(user: DashboardUser, companyId: number | null) return wrap; } -/** 회사 고르기(시스템 관리자만) — 고르면 저장 뒤 대시보드를 다시 읽음 */ -function buildCompanyPicker(state: DashboardState): HTMLElement { - const field = createSelectField({ - label: L("B01_Dashboard_PickCompany"), - options: state.allCompanies.map((c) => ({ value: String(c.id), text: c.name })), - value: String(state.adminCompanyId ?? ""), - compact: true, - onChange: (value) => { - sessionStorage.setItem(ADMIN_COMPANY_KEY, value); - window.dispatchEvent(new HashChangeEvent("hashchange")); - }, +/** 회사 메뉴 — 공용 작은 메뉴 하나를 알약 모두가 같이 씀 */ +let companyMenu: MapContextMenu | null = null; + +/** 회사 알약(시스템 관리자만) — 누르면 등록된 회사 메뉴 · 고르면 저장 뒤 대시보드를 다시 읽음 */ +function companyPill(state: DashboardState): HTMLElement { + const name = state.allCompanies.find((c) => c.id === state.adminCompanyId)?.name ?? "-"; + const pill = document.createElement("button"); + pill.type = "button"; + pill.className = "ui-tag ui-tag--accent b01-dashboard__company-pill"; + pill.title = L("B01_Dashboard_PickCompany"); + pill.textContent = `${name} ▾`; + pill.addEventListener("click", (event) => { + // 제목 줄 누름 = 접힘 — 알약 누름은 올려 보내지 않음 + event.stopPropagation(); + if (!companyMenu?.element.isConnected) { + companyMenu = createMapContextMenu("b01-dashboard"); + document.body.append(companyMenu.element); + } + const box = pill.getBoundingClientRect(); + companyMenu.open( + box.left, + box.bottom + 4, + state.allCompanies.map((c): [string, () => void] => [ + c.name, + () => { + sessionStorage.setItem(ADMIN_COMPANY_KEY, String(c.id)); + window.dispatchEvent(new HashChangeEvent("hashchange")); + }, + ]), + ); }); - field.root.classList.add("b01-dashboard__company-pick"); - // 제목 줄 누름 = 접힘 — 고르기 누름은 올려 보내지 않음 - field.root.addEventListener("click", (event) => event.stopPropagation()); - return field.root; + return pill; } /** 시스템 설정 컨테이너 — 모든 역할 · 한 줄 통째 · 제목 눌러 통째로 접힘 */ function buildSystemSettingsSection(state: DashboardState): HTMLElement { const { user } = state; - const picker = user.role === "SYSTEM_ADMIN" ? [buildCompanyPicker(state)] : []; + const pill = user.role === "SYSTEM_ADMIN" ? [companyPill(state)] : []; const panel = buildSystemSettingsPanel(user, state.adminCompanyId); - const card = section(L("B01_Dashboard_SystemSettings"), panel, true, picker); + const card = section(L("B01_Dashboard_SystemSettings"), panel, true, pill); card.querySelector(".ui-card__body")?.classList.add("ui-collapsible"); card.querySelector(".b01-dashboard__section-header")?.classList.add("ui-collapsible__title"); attachCollapsible(card); @@ -238,23 +254,31 @@ function buildPage(state: DashboardState): HTMLElement { grid.className = "b01-dashboard__grid"; if (state.user.role === "SYSTEM_ADMIN") { - // 시스템 설정은 리소스 현황 아래(29-1) · 그 아래 컨테이너는 고른 회사 것 + // 시스템 설정은 리소스 현황 아래(29-1) · 회사 알약이 붙은 컨테이너는 고른 회사 것 const picked = state.allCompanies.filter((c) => c.id === state.adminCompanyId); grid.append( section(L("B01_Dashboard_Resources"), buildResourcePanel(state.resources), true), buildSystemSettingsSection(state), section(L("B01_Dashboard_Projects"), projectTable(state.allProjects, state.user), true, [ + companyPill(state), createButton({ label: "+", onClick: () => navigateTo(ROUTES.B02_PROJ_REGISTER) }), ]), buildTempUploadSection(), section(L("B01_Dashboard_Users"), userTable(state.allUsers, state.user), true, [ + companyPill(state), createButton({ label: "+", onClick: () => openAddMemberModal(undefined, state.adminCompanyId), }), ]), - section(L("B01_Dashboard_JoinRequests"), joinRequestTable(state.allJoinRequests, true), true), + section( + L("B01_Dashboard_JoinRequests"), + joinRequestTable(state.allJoinRequests, true), + true, + [companyPill(state)], + ), section(L("B01_Dashboard_Companies"), companyTable(picked, state.user), true, [ + companyPill(state), createButton({ label: "+", onClick: () => openCreateCompanyModal() }), ]), ); diff --git a/B01_Dashboard/B01_Dashboard_UI_Style.css b/B01_Dashboard/B01_Dashboard_UI_Style.css index 0e082f38..f2c2a0a3 100644 --- a/B01_Dashboard/B01_Dashboard_UI_Style.css +++ b/B01_Dashboard/B01_Dashboard_UI_Style.css @@ -68,12 +68,47 @@ font-size: var(--text-caption); } -/* 회사 고르기 — 시스템 설정 제목 줄 오른쪽 · 라벨과 고르기를 한 줄로 */ -.ui-field.b01-dashboard__company-pick { - flex-direction: row; - align-items: center; - gap: var(--spacing-8); - margin: 0; +/* 회사 알약 — 회사를 따라 바뀌는 컨테이너 제목 오른쪽 · 누르면 회사 메뉴 */ +.b01-dashboard__company-pill { + border: 0; + font: inherit; + font-size: var(--text-caption); + cursor: pointer; +} + +.b01-dashboard__context-menu { + position: fixed; + z-index: var(--z-modal); + display: flex; + flex-direction: column; + min-width: 160px; + max-height: 60vh; + overflow-y: auto; + padding: var(--spacing-4); + border: 1px solid var(--color-border); + border-radius: var(--radius-cards); + background: var(--color-surface-raised); + box-shadow: var(--shadow-lg); +} + +.b01-dashboard__context-menu[hidden] { + display: none; +} + +.b01-dashboard__context-menu-item { + padding: var(--spacing-8) var(--spacing-12); + border: 0; + border-radius: var(--radius-cards); + background: transparent; + color: inherit; + font: inherit; + font-size: var(--text-body-sm); + text-align: left; + cursor: pointer; +} + +.b01-dashboard__context-menu-item:hover { + background: var(--color-surface-sunken); } /* 접히는 제목 줄 — 단추 칸을 접힘 표시 바로 왼쪽으로 */ diff --git a/resources/tester/test_b01_settings_cards.py b/resources/tester/test_b01_settings_cards.py index b36822cc..f51ccef1 100644 --- a/resources/tester/test_b01_settings_cards.py +++ b/resources/tester/test_b01_settings_cards.py @@ -113,26 +113,32 @@ def test_모든_역할_한_줄_통째로_접힘(): assert "StructureManage" not in src and "buildStructurePanel" not in src assert "top-row" not in src sect = _fn(src, "buildSystemSettingsSection") - assert 'L("B01_Dashboard_SystemSettings"), panel, true, picker)' in sect # 한 줄 통째 + assert 'L("B01_Dashboard_SystemSettings"), panel, true, pill)' in sect # 한 줄 통째 assert '"ui-collapsible"' in sect and '"ui-collapsible__title"' in sect assert "attachCollapsible(card)" in sect config = _text(ROOT / "config" / "config_frontend.ts") assert re.search(r"PROTECTED_ROUTES[\s\S]*?ROUTES\.M02_MASTER_TEMPLATE,[\s\S]*?\];", config) -def test_회사_고르기_시스템_관리자만(): +def test_회사_알약_시스템_관리자만(): src = _text(PAGE) sect = _fn(src, "buildSystemSettingsSection") - assert 'user.role === "SYSTEM_ADMIN" ? [buildCompanyPicker(state)] : []' in sect - picker = _fn(src, "buildCompanyPicker") - assert "sessionStorage.setItem(ADMIN_COMPANY_KEY, value)" in picker - assert "event.stopPropagation()" in picker # 고르기 누름이 접힘으로 새지 않음 + assert 'user.role === "SYSTEM_ADMIN" ? [companyPill(state)] : []' in sect + pill = _fn(src, "companyPill") + assert "sessionStorage.setItem(ADMIN_COMPANY_KEY, String(c.id))" in pill + assert "event.stopPropagation()" in pill # 알약 누름이 접힘으로 새지 않음 + assert 'createMapContextMenu("b01-dashboard")' in pill # 공용 작은 메뉴 + assert '"ui-tag ui-tag--accent b01-dashboard__company-pill"' in pill # 공용 알약 모양 assert 'const ADMIN_COMPANY_KEY = "aislo.adminCompany";' in src # M02 와 같은 키 load = src.split("async function loadRoleData")[1].split("\n}\n")[0] for call in ("fetchAllUsers(companyId)", "fetchCompanyJoinRequests(companyId)", "fetchCompanyProjects(companyId)"): # fmt: skip assert call in load, call build = _fn(src, "buildPage") + # 회사 따라 바뀌는 컨테이너 넷(프로젝트 · 사용자 관리 · 가입 요청 · 회사 관리)마다 알약 + sys_part = build.split('} else if (state.user.role === "ADMIN")')[0] + assert sys_part.count("companyPill(state)") == 4 + assert "companyPill" not in build.split('} else if (state.user.role === "ADMIN")')[1] assert "companyTable(picked, state.user)" in build assert "openAddMemberModal(undefined, state.adminCompanyId)" in build diff --git a/ui_template/ui_template_locale_b3.ts b/ui_template/ui_template_locale_b3.ts index 63b1d25d..48ad7110 100644 --- a/ui_template/ui_template_locale_b3.ts +++ b/ui_template/ui_template_locale_b3.ts @@ -145,7 +145,7 @@ export const ui_locales_b3 = { B01_Dashboard_PermCompanyRead: ["회사 읽기", "Company read"], B01_Dashboard_PermCompanyWrite: ["회사 수정", "Company edit"], B01_Dashboard_PermPersonalWrite: ["개인 수정", "Personal edit"], - B01_Dashboard_PickCompany: ["회사", "Company"], + B01_Dashboard_PickCompany: ["회사 고르기", "Pick company"], /* --- B08·B09 빈 화면 (PLAN 7-3) --- */ B08_Quantity_Rebuilding: [ From 5d97102f35e886df160b689381351a2766517f9c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 30 Sep 2026 17:37:02 +0900 Subject: [PATCH 4/6] auto: 2026-09-30 17:37 (ESD_LAPTOP) --- .../drawing/00_template_compass.json | 2761 +++++++++-------- 1 file changed, 1384 insertions(+), 1377 deletions(-) diff --git a/resources/master_template/drawing/00_template_compass.json b/resources/master_template/drawing/00_template_compass.json index 48f5f88d..9e165b59 100644 --- a/resources/master_template/drawing/00_template_compass.json +++ b/resources/master_template/drawing/00_template_compass.json @@ -1,1438 +1,1445 @@ { - "format": 6, - "source": "00_templete_compass.dxf (원본 표제란 글자 제거)", - "entities": [ - { - "id": "6d9397b2-28f9-534b-9b69-f309f7fe9846", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ + "format": 6, + "source": "00_templete_compass.dxf (원본 표제란 글자 제거)", + "entities": [ { - "id": "ee08a256-9970-52ff-a1d6-7ced755193a7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 383.83265, - "y": 240.079585 - }, - "endPoint": { - "x": 381.521334, - "y": 237.780025 - } - } + "id": "6d9397b2-28f9-534b-9b69-f309f7fe9846", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "ee08a256-9970-52ff-a1d6-7ced755193a7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 383.83265, + "y": 240.079585 + }, + "endPoint": { + "x": 381.521334, + "y": 237.780025 + } + } + }, + { + "id": "aa55c03a-61c0-5d4c-a10e-b93022d0dbef", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.521334, + "y": 237.780025 + }, + "endPoint": { + "x": 381.525649, + "y": 237.736086 + } + } + }, + { + "id": "2b52c32c-1cec-5e01-8e42-e9d58c0083ff", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.525649, + "y": 237.736086 + }, + "endPoint": { + "x": 382.190068, + "y": 237.638351 + } + } + }, + { + "id": "c8e434ac-6de7-5b23-a967-8dfbf12b2def", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 382.190068, + "y": 237.638351 + }, + "endPoint": { + "x": 383.83265, + "y": 240.079585 + } + } + } + ] }, { - "id": "aa55c03a-61c0-5d4c-a10e-b93022d0dbef", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.521334, - "y": 237.780025 - }, - "endPoint": { - "x": 381.525649, - "y": 237.736086 - } - } + "id": "52ae915b-884f-5076-972e-e2d3df4361da", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "0175fa80-3e29-553d-a1d5-285691a1b1a7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.516282, + "y": 235.798727 + }, + "endPoint": { + "x": 381.442433, + "y": 235.084356 + } + } + }, + { + "id": "91d94049-3051-5c55-8dfa-00f01c5ab89c", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.442433, + "y": 235.084356 + }, + "endPoint": { + "x": 383.815842, + "y": 233.48741 + } + } + }, + { + "id": "a44dbece-6ab7-58b1-8c6c-b4e48a89d706", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 383.815842, + "y": 233.48741 + }, + "endPoint": { + "x": 381.516282, + "y": 235.798727 + } + } + } + ] }, { - "id": "2b52c32c-1cec-5e01-8e42-e9d58c0083ff", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.525649, - "y": 237.736086 - }, - "endPoint": { - "x": 382.190068, - "y": 237.638351 - } - } + "id": "298f9cde-40ee-5f14-90b1-997ae4a1a959", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "46e587a7-6efa-592f-a5c0-399eed33b542", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.534984, + "y": 235.803778 + }, + "endPoint": { + "x": 379.530668, + "y": 235.847717 + } + } + }, + { + "id": "32a2c473-b3b8-56a4-a698-b64d4f27f018", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.530668, + "y": 235.847717 + }, + "endPoint": { + "x": 378.866249, + "y": 235.945452 + } + } + }, + { + "id": "065e534b-ec62-50a6-aff1-1a2ee31adde1", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 378.866249, + "y": 235.945452 + }, + "endPoint": { + "x": 377.223668, + "y": 233.504219 + } + } + }, + { + "id": "b904116a-5486-51b5-8623-5f191f05e5fa", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 377.223668, + "y": 233.504219 + }, + "endPoint": { + "x": 379.534984, + "y": 235.803778 + } + } + } + ] }, { - "id": "c8e434ac-6de7-5b23-a967-8dfbf12b2def", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 382.190068, - "y": 237.638351 - }, - "endPoint": { - "x": 383.83265, - "y": 240.079585 - } - } - } - ] - }, - { - "id": "52ae915b-884f-5076-972e-e2d3df4361da", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "0175fa80-3e29-553d-a1d5-285691a1b1a7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.516282, - "y": 235.798727 - }, - "endPoint": { - "x": 381.442433, - "y": 235.084356 - } - } + "id": "b552ef8f-e8fd-53f7-8b52-16fc785bae44", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "7d6abbf4-688a-592c-ac2b-e340ec32a649", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.540036, + "y": 237.785076 + }, + "endPoint": { + "x": 379.613885, + "y": 238.499447 + } + } + }, + { + "id": "8e174be7-d37f-50c3-a1b4-fc5e9db3c7ba", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.613885, + "y": 238.499447 + }, + "endPoint": { + "x": 377.240476, + "y": 240.096393 + } + } + }, + { + "id": "28342a6e-4ea2-5e55-ab34-0c41a9127378", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 377.240476, + "y": 240.096393 + }, + "endPoint": { + "x": 379.540036, + "y": 237.785076 + } + } + } + ] }, { - "id": "91d94049-3051-5c55-8dfa-00f01c5ab89c", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.442433, - "y": 235.084356 - }, - "endPoint": { - "x": 383.815842, - "y": 233.48741 - } - } + "id": "407f4a25-36f3-50ba-9836-80e859bcf9f8", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "56785032-420c-5ce4-9171-237469dc69e7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 379.534984, + "y": 235.803778 + } + } + }, + { + "id": "52171c83-b13b-5d4f-96d2-619312c5b8fe", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.534984, + "y": 235.803778 + }, + "endPoint": { + "x": 380.500575, + "y": 225.9734 + } + } + }, + { + "id": "e0fb6280-d368-5346-a923-5325c041240a", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.500575, + "y": 225.9734 + }, + "endPoint": { + "x": 380.508916, + "y": 229.24492 + } + } + }, + { + "id": "b799031c-9cc1-527c-a3b6-568d7043b09b", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.508916, + "y": 229.24492 + }, + "endPoint": { + "x": 380.528159, + "y": 236.791902 + } + } + } + ] }, { - "id": "a44dbece-6ab7-58b1-8c6c-b4e48a89d706", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 383.815842, - "y": 233.48741 - }, - "endPoint": { - "x": 381.516282, - "y": 235.798727 - } - } - } - ] - }, - { - "id": "298f9cde-40ee-5f14-90b1-997ae4a1a959", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "46e587a7-6efa-592f-a5c0-399eed33b542", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.534984, - "y": 235.803778 - }, - "endPoint": { - "x": 379.530668, - "y": 235.847717 - } - } + "id": "36439b23-1504-59b3-932f-fe3bc2042d61", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "4b8bbad8-2ca4-5cd9-9b7f-b6a567e29194", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 372.981177, + "y": 236.811144 + }, + "endPoint": { + "x": 380.528159, + "y": 236.791902 + } + } + }, + { + "id": "6461eddb-65ab-5081-953a-3e479e07f115", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 379.577761, + "y": 237.747158 + } + } + }, + { + "id": "c997d76e-da7d-5d58-adfd-14f83abaca18", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.577761, + "y": 237.747158 + }, + "endPoint": { + "x": 372.981177, + "y": 236.811144 + } + } + } + ] }, { - "id": "32a2c473-b3b8-56a4-a698-b64d4f27f018", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.530668, - "y": 235.847717 - }, - "endPoint": { - "x": 378.866249, - "y": 235.945452 - } - } + "id": "110e6249-d765-58a5-b513-83873e1a09dd", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "ae89c362-3f17-5bc3-8ca2-791f07f98a1a", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.555743, + "y": 247.610403 + }, + "endPoint": { + "x": 380.528159, + "y": 236.791902 + } + } + }, + { + "id": "98d64c6c-6856-5925-8739-37725b69595a", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 381.521334, + "y": 237.780025 + } + } + }, + { + "id": "f3db0b69-00fd-5ac2-86ce-91aab356a253", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.521334, + "y": 237.780025 + }, + "endPoint": { + "x": 380.555743, + "y": 247.610403 + } + } + } + ] }, { - "id": "065e534b-ec62-50a6-aff1-1a2ee31adde1", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 378.866249, - "y": 235.945452 - }, - "endPoint": { - "x": 377.223668, - "y": 233.504219 - } - } + "id": "0b231456-c8ff-5c78-8df3-65c06e9fad88", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "df1661e1-c643-58e8-8025-1a200e7c7efa", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 388.075141, + "y": 236.772659 + }, + "endPoint": { + "x": 380.528159, + "y": 236.791902 + } + } + }, + { + "id": "69f3977d-840d-568a-ad7b-fad69dfb8928", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 381.478556, + "y": 235.836645 + } + } + }, + { + "id": "dfa3951a-ed6b-54c0-af72-1729b2f122f3", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.478556, + "y": 235.836645 + }, + "endPoint": { + "x": 388.075141, + "y": 236.772659 + } + } + } + ] }, { - "id": "b904116a-5486-51b5-8623-5f191f05e5fa", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 377.223668, - "y": 233.504219 - }, - "endPoint": { - "x": 379.534984, - "y": 235.803778 - } - } - } - ] - }, - { - "id": "b552ef8f-e8fd-53f7-8b52-16fc785bae44", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "7d6abbf4-688a-592c-ac2b-e340ec32a649", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.540036, - "y": 237.785076 - }, - "endPoint": { - "x": 379.613885, - "y": 238.499447 - } - } + "id": "bf5d42a0-72e6-5e69-9aeb-9536d999e2d8", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "a8820faf-4457-52a4-a077-cdf88af46662", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.567469, + "y": 252.209497 + }, + "endPoint": { + "x": 380.493656, + "y": 221.40732 + } + } + } + ] }, { - "id": "8e174be7-d37f-50c3-a1b4-fc5e9db3c7ba", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.613885, - "y": 238.499447 - }, - "endPoint": { - "x": 377.240476, - "y": 240.096393 - } - } + "id": "5f5337b1-d9af-5371-80bb-0b82c9b0c982", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "a37ca433-5694-569d-b058-babbbebb1e9d", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.577761, + "y": 237.747158 + }, + "endPoint": { + "x": 372.981177, + "y": 236.811144 + } + } + }, + { + "id": "2222c71d-84e0-502f-ad1c-9df2bc35358c", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 372.981177, + "y": 236.811144 + }, + "endPoint": { + "x": 379.572902, + "y": 235.841504 + } + } + } + ] }, { - "id": "28342a6e-4ea2-5e55-ab34-0c41a9127378", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 377.240476, - "y": 240.096393 - }, - "endPoint": { - "x": 379.540036, - "y": 237.785076 - } - } - } - ] - }, - { - "id": "407f4a25-36f3-50ba-9836-80e859bcf9f8", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "56785032-420c-5ce4-9171-237469dc69e7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 379.534984, - "y": 235.803778 - } - } + "id": "68aebd36-2bcf-5651-affe-643fe976079f", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "b4f79eda-8381-5dea-93f1-7ef1934b34bd", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 377.240476, + "y": 240.096393 + } + } + } + ] }, { - "id": "52171c83-b13b-5d4f-96d2-619312c5b8fe", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.534984, - "y": 235.803778 - }, - "endPoint": { - "x": 380.500575, - "y": 225.9734 - } - } + "id": "f67ff1b1-4ef6-565b-9f59-554a2fb76b87", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "879b8834-d18e-51a2-817e-44b03837bc73", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 380.530939, + "y": 237.882408 + } + } + } + ] }, { - "id": "e0fb6280-d368-5346-a923-5325c041240a", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.500575, - "y": 225.9734 - }, - "endPoint": { - "x": 380.508916, - "y": 229.24492 - } - } + "id": "e412b991-a74c-5a7b-9912-58777bc381dd", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "73ff73da-587c-5825-979c-24a2bfe792ba", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.478556, + "y": 235.836645 + }, + "endPoint": { + "x": 388.075141, + "y": 236.772659 + } + } + }, + { + "id": "66e9e781-4aa9-5af6-9ea3-e9c8231c51bb", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 388.075141, + "y": 236.772659 + }, + "endPoint": { + "x": 381.483415, + "y": 237.742299 + } + } + } + ] }, { - "id": "b799031c-9cc1-527c-a3b6-568d7043b09b", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.508916, - "y": 229.24492 - }, - "endPoint": { - "x": 380.528159, - "y": 236.791902 - } - } - } - ] - }, - { - "id": "36439b23-1504-59b3-932f-fe3bc2042d61", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "4b8bbad8-2ca4-5cd9-9b7f-b6a567e29194", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 372.981177, - "y": 236.811144 - }, - "endPoint": { - "x": 380.528159, - "y": 236.791902 - } - } + "id": "0db65160-baf3-5377-a0df-4dbefff6efc1", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "330f9c78-f00d-51f6-9203-56368424d91d", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 383.83265, + "y": 240.079585 + } + } + } + ] }, { - "id": "6461eddb-65ab-5081-953a-3e479e07f115", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 379.577761, - "y": 237.747158 - } - } + "id": "0b507838-c0a0-53fa-8091-cb516a60f62d", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "6e65c446-e85b-5272-8a3d-d9cb162aefc6", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 370.346907, + "y": 236.82218 + }, + "endPoint": { + "x": 390.820396, + "y": 236.76134 + } + } + } + ] }, { - "id": "c997d76e-da7d-5d58-adfd-14f83abaca18", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.577761, - "y": 237.747158 - }, - "endPoint": { - "x": 372.981177, - "y": 236.811144 - } - } - } - ] - }, - { - "id": "110e6249-d765-58a5-b513-83873e1a09dd", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "ae89c362-3f17-5bc3-8ca2-791f07f98a1a", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.555743, - "y": 247.610403 - }, - "endPoint": { - "x": 380.528159, - "y": 236.791902 - } - } + "id": "78eb244d-8bb5-5117-a14e-a79adc1b9799", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "cd29b354-0165-5e86-90c1-e3435bcb8da7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.508916, + "y": 229.24492 + }, + "endPoint": { + "x": 380.500575, + "y": 225.9734 + } + } + }, + { + "id": "6695f801-d4a6-5255-bc57-90f74ec46ffa", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.500575, + "y": 225.9734 + }, + "endPoint": { + "x": 381.516282, + "y": 235.798727 + } + } + } + ] }, { - "id": "98d64c6c-6856-5925-8739-37725b69595a", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 381.521334, - "y": 237.780025 - } - } + "id": "1c9d17c7-109f-52c5-a106-124b9d8bb6ea", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "59f82737-841f-5012-8da5-0bfe909bba19", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.534984, + "y": 235.803778 + }, + "endPoint": { + "x": 380.500575, + "y": 225.9734 + } + } + } + ] }, { - "id": "f3db0b69-00fd-5ac2-86ce-91aab356a253", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.521334, - "y": 237.780025 - }, - "endPoint": { - "x": 380.555743, - "y": 247.610403 - } - } - } - ] - }, - { - "id": "0b231456-c8ff-5c78-8df3-65c06e9fad88", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "df1661e1-c643-58e8-8025-1a200e7c7efa", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 388.075141, - "y": 236.772659 - }, - "endPoint": { - "x": 380.528159, - "y": 236.791902 - } - } + "id": "74b9bbbe-df6e-5ce3-9239-e3209870598f", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "ab249891-3e19-5147-a841-5d2b0866c0d7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 377.223668, + "y": 233.504219 + } + } + } + ] }, { - "id": "69f3977d-840d-568a-ad7b-fad69dfb8928", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 381.478556, - "y": 235.836645 - } - } + "id": "dcb8b38d-6185-575f-b4ee-b00afdc304ec", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "f0fc86dd-c655-58c5-aa78-83bf25f535b2", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 379.437652, + "y": 236.794682 + } + } + } + ] }, { - "id": "dfa3951a-ed6b-54c0-af72-1729b2f122f3", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.478556, - "y": 235.836645 - }, - "endPoint": { - "x": 388.075141, - "y": 236.772659 - } - } - } - ] - }, - { - "id": "bf5d42a0-72e6-5e69-9aeb-9536d999e2d8", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "a8820faf-4457-52a4-a077-cdf88af46662", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.567469, - "y": 252.209497 - }, - "endPoint": { - "x": 380.493656, - "y": 221.40732 - } - } - } - ] - }, - { - "id": "5f5337b1-d9af-5371-80bb-0b82c9b0c982", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "a37ca433-5694-569d-b058-babbbebb1e9d", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.577761, - "y": 237.747158 - }, - "endPoint": { - "x": 372.981177, - "y": 236.811144 - } - } + "id": "7697866f-d339-58db-96f9-0088337ac6cc", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "96083634-c390-59fe-b433-38f85c3ed1d0", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.605189, + "y": 235.08904 + }, + "endPoint": { + "x": 377.223668, + "y": 233.504219 + } + } + }, + { + "id": "eac5b79b-02d1-5d5b-969f-4baea7bad673", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 377.223668, + "y": 233.504219 + }, + "endPoint": { + "x": 378.866249, + "y": 235.945452 + } + } + } + ] }, { - "id": "2222c71d-84e0-502f-ad1c-9df2bc35358c", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 372.981177, - "y": 236.811144 - }, - "endPoint": { - "x": 379.572902, - "y": 235.841504 - } - } - } - ] - }, - { - "id": "68aebd36-2bcf-5651-affe-643fe976079f", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "b4f79eda-8381-5dea-93f1-7ef1934b34bd", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 377.240476, - "y": 240.096393 - } - } - } - ] - }, - { - "id": "f67ff1b1-4ef6-565b-9f59-554a2fb76b87", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "879b8834-d18e-51a2-817e-44b03837bc73", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 380.530939, - "y": 237.882408 - } - } - } - ] - }, - { - "id": "e412b991-a74c-5a7b-9912-58777bc381dd", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "73ff73da-587c-5825-979c-24a2bfe792ba", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.478556, - "y": 235.836645 - }, - "endPoint": { - "x": 388.075141, - "y": 236.772659 - } - } + "id": "c1dfcf25-62e9-538c-8f5d-66ad9e1540ac", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "691a2869-06a4-572f-8bfc-98047ab1bd48", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.530668, + "y": 235.847717 + }, + "endPoint": { + "x": 379.534984, + "y": 235.803778 + } + } + } + ] }, { - "id": "66e9e781-4aa9-5af6-9ea3-e9c8231c51bb", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 388.075141, - "y": 236.772659 - }, - "endPoint": { - "x": 381.483415, - "y": 237.742299 - } - } - } - ] - }, - { - "id": "0db65160-baf3-5377-a0df-4dbefff6efc1", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "330f9c78-f00d-51f6-9203-56368424d91d", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 383.83265, - "y": 240.079585 - } - } - } - ] - }, - { - "id": "0b507838-c0a0-53fa-8091-cb516a60f62d", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "6e65c446-e85b-5272-8a3d-d9cb162aefc6", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 370.346907, - "y": 236.82218 - }, - "endPoint": { - "x": 390.820396, - "y": 236.76134 - } - } - } - ] - }, - { - "id": "78eb244d-8bb5-5117-a14e-a79adc1b9799", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "cd29b354-0165-5e86-90c1-e3435bcb8da7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.508916, - "y": 229.24492 - }, - "endPoint": { - "x": 380.500575, - "y": 225.9734 - } - } + "id": "4777d036-1245-5d4d-8ea7-8230286c9255", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "defa3147-faae-506b-9d69-dc7e7efb7157", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 383.815842, + "y": 233.48741 + } + } + } + ] }, { - "id": "6695f801-d4a6-5255-bc57-90f74ec46ffa", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.500575, - "y": 225.9734 - }, - "endPoint": { - "x": 381.516282, - "y": 235.798727 - } - } - } - ] - }, - { - "id": "1c9d17c7-109f-52c5-a106-124b9d8bb6ea", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "59f82737-841f-5012-8da5-0bfe909bba19", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.534984, - "y": 235.803778 - }, - "endPoint": { - "x": 380.500575, - "y": 225.9734 - } - } - } - ] - }, - { - "id": "74b9bbbe-df6e-5ce3-9239-e3209870598f", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "ab249891-3e19-5147-a841-5d2b0866c0d7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 377.223668, - "y": 233.504219 - } - } - } - ] - }, - { - "id": "dcb8b38d-6185-575f-b4ee-b00afdc304ec", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "f0fc86dd-c655-58c5-aa78-83bf25f535b2", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 379.437652, - "y": 236.794682 - } - } - } - ] - }, - { - "id": "7697866f-d339-58db-96f9-0088337ac6cc", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "96083634-c390-59fe-b433-38f85c3ed1d0", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.605189, - "y": 235.08904 - }, - "endPoint": { - "x": 377.223668, - "y": 233.504219 - } - } + "id": "e2519a98-ef75-55b5-90b5-e755ed30f1d0", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "63cea5cf-a790-57b1-8ed2-a5bf1218846a", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 382.18573, + "y": 235.936989 + }, + "endPoint": { + "x": 383.815842, + "y": 233.48741 + } + } + }, + { + "id": "39e81350-6708-5b6c-9207-1a8d50311de2", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 383.815842, + "y": 233.48741 + }, + "endPoint": { + "x": 381.442433, + "y": 235.084356 + } + } + } + ] }, { - "id": "eac5b79b-02d1-5d5b-969f-4baea7bad673", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 377.223668, - "y": 233.504219 - }, - "endPoint": { - "x": 378.866249, - "y": 235.945452 - } - } - } - ] - }, - { - "id": "c1dfcf25-62e9-538c-8f5d-66ad9e1540ac", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "691a2869-06a4-572f-8bfc-98047ab1bd48", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.530668, - "y": 235.847717 - }, - "endPoint": { - "x": 379.534984, - "y": 235.803778 - } - } - } - ] - }, - { - "id": "4777d036-1245-5d4d-8ea7-8230286c9255", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "defa3147-faae-506b-9d69-dc7e7efb7157", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 383.815842, - "y": 233.48741 - } - } - } - ] - }, - { - "id": "e2519a98-ef75-55b5-90b5-e755ed30f1d0", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "63cea5cf-a790-57b1-8ed2-a5bf1218846a", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 382.18573, - "y": 235.936989 - }, - "endPoint": { - "x": 383.815842, - "y": 233.48741 - } - } + "id": "97914abe-6b82-53a4-b222-ee1cb1af8ef2", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "75f3d793-39e8-5654-a1e1-aef11e877388", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 380.525378, + "y": 235.701395 + } + } + } + ] }, { - "id": "39e81350-6708-5b6c-9207-1a8d50311de2", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 383.815842, - "y": 233.48741 - }, - "endPoint": { - "x": 381.442433, - "y": 235.084356 - } - } - } - ] - }, - { - "id": "97914abe-6b82-53a4-b222-ee1cb1af8ef2", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "75f3d793-39e8-5654-a1e1-aef11e877388", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 380.525378, - "y": 235.701395 - } - } - } - ] - }, - { - "id": "3e6e8323-45fe-596b-8fac-0c161ec75934", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "416d5122-af17-5c40-bad6-bc536a7f984f", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.516282, - "y": 235.798727 - }, - "endPoint": { - "x": 381.520822, - "y": 235.842642 - } - } - } - ] - }, - { - "id": "14e440df-1743-5335-9ee6-83f33dc0fefa", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "53584a43-8c24-5d46-94f1-d03bf48af315", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 381.618665, - "y": 236.789121 - } - } - } - ] - }, - { - "id": "b3787d17-5449-5a53-9abe-fb8305433759", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "1d3f850f-597e-5adb-9d9e-d39941552a47", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.567469, - "y": 252.209497 - }, - "endPoint": { - "x": 380.106447, - "y": 250.393027 - } - } + "id": "3e6e8323-45fe-596b-8fac-0c161ec75934", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "416d5122-af17-5c40-bad6-bc536a7f984f", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.516282, + "y": 235.798727 + }, + "endPoint": { + "x": 381.520822, + "y": 235.842642 + } + } + } + ] }, { - "id": "06fca6d7-469f-5498-a346-51fde73e5506", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.106447, - "y": 250.393027 - }, - "endPoint": { - "x": 381.007941, - "y": 249.915905 - } - } - } - ] - }, - { - "id": "02b36675-7c0c-551f-8df6-2264393e0c5a", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "910e40a6-09c8-5733-92f0-0e4bf9423a19", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.555743, - "y": 247.610403 - }, - "endPoint": { - "x": 381.525649, - "y": 237.736086 - } - } - } - ] - }, - { - "id": "16c615e2-b657-5aa6-b91f-bfc7e6585df4", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "6638b938-a363-5c05-a9df-0b46bfb97dd3", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.547401, - "y": 244.338883 - }, - "endPoint": { - "x": 380.555743, - "y": 247.610403 - } - } + "id": "14e440df-1743-5335-9ee6-83f33dc0fefa", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "53584a43-8c24-5d46-94f1-d03bf48af315", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 381.618665, + "y": 236.789121 + } + } + } + ] }, { - "id": "528740e4-ad57-5d7c-9fe8-240f65378a94", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.555743, - "y": 247.610403 - }, - "endPoint": { - "x": 379.540036, - "y": 237.785076 - } - } - } - ] - }, - { - "id": "48e5cea5-af62-5b8a-a4c5-ff006321b856", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "778ba35b-051d-5e65-b587-f0be9103061d", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 378.870587, - "y": 237.646814 - }, - "endPoint": { - "x": 377.240476, - "y": 240.096393 - } - } + "id": "b3787d17-5449-5a53-9abe-fb8305433759", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "5bc7a805-e564-4e10-a5c6-08770dee7374", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.567469, + "y": 252.209497 + }, + "endPoint": { + "x": 380.106447, + "y": 250.393027 + } + } + }, + { + "id": "65715537-5650-43e5-aef7-784c3c16621b", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.106447, + "y": 250.393027 + }, + "endPoint": { + "x": 380.5619857896512, + "y": 249.92135242256356 + } + } + } + ] }, { - "id": "894702be-b383-5dea-87d6-3cc27bff6583", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 377.240476, - "y": 240.096393 - }, - "endPoint": { - "x": 379.613885, - "y": 238.499447 - } - } - } - ] - }, - { - "id": "345495ee-1586-525d-847c-b7374e38df95", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "016e7bd7-3f6b-53f6-af66-b1fecddba3f1", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.577761, - "y": 237.747158 - }, - "endPoint": { - "x": 379.535496, - "y": 237.741161 - } - } - } - ] - }, - { - "id": "01a8f807-b222-5fb1-999e-b6b0f014b5f4", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "02b029f3-380b-5157-8cc7-84d4c4615452", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.540036, - "y": 237.785076 - }, - "endPoint": { - "x": 379.535496, - "y": 237.741161 - } - } - } - ] - }, - { - "id": "c3f8ba49-82a1-5c42-b62e-98cacc8de059", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "ab907944-26b1-5be9-a8fd-7cd38509c27b", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.525649, - "y": 237.736086 - }, - "endPoint": { - "x": 381.483415, - "y": 237.742299 - } - } - } - ] - }, - { - "id": "ff01dc0d-6daa-55b2-823d-33d9571c8040", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "62edb0f7-dce4-5d20-90b7-be4416de28be", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.451128, - "y": 238.494763 - }, - "endPoint": { - "x": 383.83265, - "y": 240.079585 - } - } + "id": "02b36675-7c0c-551f-8df6-2264393e0c5a", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "910e40a6-09c8-5733-92f0-0e4bf9423a19", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.555743, + "y": 247.610403 + }, + "endPoint": { + "x": 381.525649, + "y": 237.736086 + } + } + } + ] }, { - "id": "b13141d0-b9ef-51ca-8311-37576e4a85d9", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 383.83265, - "y": 240.079585 - }, - "endPoint": { - "x": 382.190068, - "y": 237.638351 - } - } - } - ] - }, - { - "id": "b5c4fdd4-1e30-5a46-9ab3-8c7bbf72898e", - "type": "Text", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "label": "N", - "basePoint": { - "x": 380.569612, - "y": 254.33105 + "id": "16c615e2-b657-5aa6-b91f-bfc7e6585df4", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "6638b938-a363-5c05-a9df-0b46bfb97dd3", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.547401, + "y": 244.338883 + }, + "endPoint": { + "x": 380.555743, + "y": 247.610403 + } + } + }, + { + "id": "528740e4-ad57-5d7c-9fe8-240f65378a94", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.555743, + "y": 247.610403 + }, + "endPoint": { + "x": 379.540036, + "y": 237.785076 + } + } + } + ] }, - "options": { - "textDirection": { - "x": 1.0, - "y": 0.0 - }, - "textAlign": "center", - "textColor": "#a55200", - "fontSize": 2.2589, - "fontFamily": "sans-serif" + { + "id": "48e5cea5-af62-5b8a-a4c5-ff006321b856", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "778ba35b-051d-5e65-b587-f0be9103061d", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 378.870587, + "y": 237.646814 + }, + "endPoint": { + "x": 377.240476, + "y": 240.096393 + } + } + }, + { + "id": "894702be-b383-5dea-87d6-3cc27bff6583", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 377.240476, + "y": 240.096393 + }, + "endPoint": { + "x": 379.613885, + "y": 238.499447 + } + } + } + ] + }, + { + "id": "345495ee-1586-525d-847c-b7374e38df95", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "016e7bd7-3f6b-53f6-af66-b1fecddba3f1", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.577761, + "y": 237.747158 + }, + "endPoint": { + "x": 379.535496, + "y": 237.741161 + } + } + } + ] + }, + { + "id": "01a8f807-b222-5fb1-999e-b6b0f014b5f4", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "02b029f3-380b-5157-8cc7-84d4c4615452", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.540036, + "y": 237.785076 + }, + "endPoint": { + "x": 379.535496, + "y": 237.741161 + } + } + } + ] + }, + { + "id": "c3f8ba49-82a1-5c42-b62e-98cacc8de059", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "ab907944-26b1-5be9-a8fd-7cd38509c27b", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.525649, + "y": 237.736086 + }, + "endPoint": { + "x": 381.483415, + "y": 237.742299 + } + } + } + ] + }, + { + "id": "ff01dc0d-6daa-55b2-823d-33d9571c8040", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "62edb0f7-dce4-5d20-90b7-be4416de28be", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.451128, + "y": 238.494763 + }, + "endPoint": { + "x": 383.83265, + "y": 240.079585 + } + } + }, + { + "id": "b13141d0-b9ef-51ca-8311-37576e4a85d9", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 383.83265, + "y": 240.079585 + }, + "endPoint": { + "x": 382.190068, + "y": 237.638351 + } + } + } + ] + }, + { + "id": "b5c4fdd4-1e30-5a46-9ab3-8c7bbf72898e", + "type": "Text", + "lineColor": "#a55200", + "lineWidth": 1, + "lineDash": [], + "layerId": "-00.기본BOX", + "shapeData": { + "label": "N", + "basePoint": { + "x": 380.569612, + "y": 254.33105 + }, + "options": { + "textDirection": { + "x": 1, + "y": 0 + }, + "textAlign": "center", + "textColor": "#a55200", + "fontSize": 2.2589, + "fontFamily": "sans-serif" + } + } } - } - } - ], - "layers": [ - { - "id": "-00.기본BOX TEXT", - "name": "-00.기본BOX TEXT", - "isVisible": true, - "isLocked": false - }, - { - "id": "-00.기본BOX", - "name": "-00.기본BOX", - "isVisible": true, - "isLocked": false - } - ] + ], + "layers": [ + { + "id": "-00.기본BOX TEXT", + "name": "-00.기본BOX TEXT", + "isVisible": true, + "isLocked": false + }, + { + "id": "-00.기본BOX", + "name": "-00.기본BOX", + "isVisible": true, + "isLocked": false + } + ], + "drawing_area": [ + 42, + 47, + 812, + 567 + ] } From 5a206fc2fe045de27730b03cb9683273edd956c5 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 30 Sep 2026 17:38:33 +0900 Subject: [PATCH 5/6] Revert "auto: 2026-09-30 17:37 (ESD_LAPTOP)" This reverts commit 5d97102f35e886df160b689381351a2766517f9c. --- .../drawing/00_template_compass.json | 2869 ++++++++--------- 1 file changed, 1431 insertions(+), 1438 deletions(-) diff --git a/resources/master_template/drawing/00_template_compass.json b/resources/master_template/drawing/00_template_compass.json index 9e165b59..48f5f88d 100644 --- a/resources/master_template/drawing/00_template_compass.json +++ b/resources/master_template/drawing/00_template_compass.json @@ -1,1445 +1,1438 @@ { - "format": 6, - "source": "00_templete_compass.dxf (원본 표제란 글자 제거)", - "entities": [ + "format": 6, + "source": "00_templete_compass.dxf (원본 표제란 글자 제거)", + "entities": [ + { + "id": "6d9397b2-28f9-534b-9b69-f309f7fe9846", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ { - "id": "6d9397b2-28f9-534b-9b69-f309f7fe9846", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "ee08a256-9970-52ff-a1d6-7ced755193a7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 383.83265, - "y": 240.079585 - }, - "endPoint": { - "x": 381.521334, - "y": 237.780025 - } - } - }, - { - "id": "aa55c03a-61c0-5d4c-a10e-b93022d0dbef", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.521334, - "y": 237.780025 - }, - "endPoint": { - "x": 381.525649, - "y": 237.736086 - } - } - }, - { - "id": "2b52c32c-1cec-5e01-8e42-e9d58c0083ff", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.525649, - "y": 237.736086 - }, - "endPoint": { - "x": 382.190068, - "y": 237.638351 - } - } - }, - { - "id": "c8e434ac-6de7-5b23-a967-8dfbf12b2def", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 382.190068, - "y": 237.638351 - }, - "endPoint": { - "x": 383.83265, - "y": 240.079585 - } - } - } - ] - }, - { - "id": "52ae915b-884f-5076-972e-e2d3df4361da", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "0175fa80-3e29-553d-a1d5-285691a1b1a7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.516282, - "y": 235.798727 - }, - "endPoint": { - "x": 381.442433, - "y": 235.084356 - } - } - }, - { - "id": "91d94049-3051-5c55-8dfa-00f01c5ab89c", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.442433, - "y": 235.084356 - }, - "endPoint": { - "x": 383.815842, - "y": 233.48741 - } - } - }, - { - "id": "a44dbece-6ab7-58b1-8c6c-b4e48a89d706", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 383.815842, - "y": 233.48741 - }, - "endPoint": { - "x": 381.516282, - "y": 235.798727 - } - } - } - ] - }, - { - "id": "298f9cde-40ee-5f14-90b1-997ae4a1a959", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "46e587a7-6efa-592f-a5c0-399eed33b542", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.534984, - "y": 235.803778 - }, - "endPoint": { - "x": 379.530668, - "y": 235.847717 - } - } - }, - { - "id": "32a2c473-b3b8-56a4-a698-b64d4f27f018", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.530668, - "y": 235.847717 - }, - "endPoint": { - "x": 378.866249, - "y": 235.945452 - } - } - }, - { - "id": "065e534b-ec62-50a6-aff1-1a2ee31adde1", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 378.866249, - "y": 235.945452 - }, - "endPoint": { - "x": 377.223668, - "y": 233.504219 - } - } - }, - { - "id": "b904116a-5486-51b5-8623-5f191f05e5fa", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 377.223668, - "y": 233.504219 - }, - "endPoint": { - "x": 379.534984, - "y": 235.803778 - } - } - } - ] - }, - { - "id": "b552ef8f-e8fd-53f7-8b52-16fc785bae44", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "7d6abbf4-688a-592c-ac2b-e340ec32a649", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.540036, - "y": 237.785076 - }, - "endPoint": { - "x": 379.613885, - "y": 238.499447 - } - } - }, - { - "id": "8e174be7-d37f-50c3-a1b4-fc5e9db3c7ba", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.613885, - "y": 238.499447 - }, - "endPoint": { - "x": 377.240476, - "y": 240.096393 - } - } - }, - { - "id": "28342a6e-4ea2-5e55-ab34-0c41a9127378", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 377.240476, - "y": 240.096393 - }, - "endPoint": { - "x": 379.540036, - "y": 237.785076 - } - } - } - ] - }, - { - "id": "407f4a25-36f3-50ba-9836-80e859bcf9f8", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "56785032-420c-5ce4-9171-237469dc69e7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 379.534984, - "y": 235.803778 - } - } - }, - { - "id": "52171c83-b13b-5d4f-96d2-619312c5b8fe", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.534984, - "y": 235.803778 - }, - "endPoint": { - "x": 380.500575, - "y": 225.9734 - } - } - }, - { - "id": "e0fb6280-d368-5346-a923-5325c041240a", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.500575, - "y": 225.9734 - }, - "endPoint": { - "x": 380.508916, - "y": 229.24492 - } - } - }, - { - "id": "b799031c-9cc1-527c-a3b6-568d7043b09b", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.508916, - "y": 229.24492 - }, - "endPoint": { - "x": 380.528159, - "y": 236.791902 - } - } - } - ] - }, - { - "id": "36439b23-1504-59b3-932f-fe3bc2042d61", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "4b8bbad8-2ca4-5cd9-9b7f-b6a567e29194", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 372.981177, - "y": 236.811144 - }, - "endPoint": { - "x": 380.528159, - "y": 236.791902 - } - } - }, - { - "id": "6461eddb-65ab-5081-953a-3e479e07f115", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 379.577761, - "y": 237.747158 - } - } - }, - { - "id": "c997d76e-da7d-5d58-adfd-14f83abaca18", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.577761, - "y": 237.747158 - }, - "endPoint": { - "x": 372.981177, - "y": 236.811144 - } - } - } - ] - }, - { - "id": "110e6249-d765-58a5-b513-83873e1a09dd", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "ae89c362-3f17-5bc3-8ca2-791f07f98a1a", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.555743, - "y": 247.610403 - }, - "endPoint": { - "x": 380.528159, - "y": 236.791902 - } - } - }, - { - "id": "98d64c6c-6856-5925-8739-37725b69595a", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 381.521334, - "y": 237.780025 - } - } - }, - { - "id": "f3db0b69-00fd-5ac2-86ce-91aab356a253", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.521334, - "y": 237.780025 - }, - "endPoint": { - "x": 380.555743, - "y": 247.610403 - } - } - } - ] - }, - { - "id": "0b231456-c8ff-5c78-8df3-65c06e9fad88", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "df1661e1-c643-58e8-8025-1a200e7c7efa", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 388.075141, - "y": 236.772659 - }, - "endPoint": { - "x": 380.528159, - "y": 236.791902 - } - } - }, - { - "id": "69f3977d-840d-568a-ad7b-fad69dfb8928", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 381.478556, - "y": 235.836645 - } - } - }, - { - "id": "dfa3951a-ed6b-54c0-af72-1729b2f122f3", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.478556, - "y": 235.836645 - }, - "endPoint": { - "x": 388.075141, - "y": 236.772659 - } - } - } - ] - }, - { - "id": "bf5d42a0-72e6-5e69-9aeb-9536d999e2d8", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "a8820faf-4457-52a4-a077-cdf88af46662", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.567469, - "y": 252.209497 - }, - "endPoint": { - "x": 380.493656, - "y": 221.40732 - } - } - } - ] - }, - { - "id": "5f5337b1-d9af-5371-80bb-0b82c9b0c982", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "a37ca433-5694-569d-b058-babbbebb1e9d", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.577761, - "y": 237.747158 - }, - "endPoint": { - "x": 372.981177, - "y": 236.811144 - } - } - }, - { - "id": "2222c71d-84e0-502f-ad1c-9df2bc35358c", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 372.981177, - "y": 236.811144 - }, - "endPoint": { - "x": 379.572902, - "y": 235.841504 - } - } - } - ] - }, - { - "id": "68aebd36-2bcf-5651-affe-643fe976079f", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "b4f79eda-8381-5dea-93f1-7ef1934b34bd", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 377.240476, - "y": 240.096393 - } - } - } - ] - }, - { - "id": "f67ff1b1-4ef6-565b-9f59-554a2fb76b87", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "879b8834-d18e-51a2-817e-44b03837bc73", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 380.530939, - "y": 237.882408 - } - } - } - ] - }, - { - "id": "e412b991-a74c-5a7b-9912-58777bc381dd", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "73ff73da-587c-5825-979c-24a2bfe792ba", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.478556, - "y": 235.836645 - }, - "endPoint": { - "x": 388.075141, - "y": 236.772659 - } - } - }, - { - "id": "66e9e781-4aa9-5af6-9ea3-e9c8231c51bb", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 388.075141, - "y": 236.772659 - }, - "endPoint": { - "x": 381.483415, - "y": 237.742299 - } - } - } - ] - }, - { - "id": "0db65160-baf3-5377-a0df-4dbefff6efc1", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "330f9c78-f00d-51f6-9203-56368424d91d", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 383.83265, - "y": 240.079585 - } - } - } - ] - }, - { - "id": "0b507838-c0a0-53fa-8091-cb516a60f62d", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "6e65c446-e85b-5272-8a3d-d9cb162aefc6", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 370.346907, - "y": 236.82218 - }, - "endPoint": { - "x": 390.820396, - "y": 236.76134 - } - } - } - ] - }, - { - "id": "78eb244d-8bb5-5117-a14e-a79adc1b9799", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "cd29b354-0165-5e86-90c1-e3435bcb8da7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.508916, - "y": 229.24492 - }, - "endPoint": { - "x": 380.500575, - "y": 225.9734 - } - } - }, - { - "id": "6695f801-d4a6-5255-bc57-90f74ec46ffa", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.500575, - "y": 225.9734 - }, - "endPoint": { - "x": 381.516282, - "y": 235.798727 - } - } - } - ] - }, - { - "id": "1c9d17c7-109f-52c5-a106-124b9d8bb6ea", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "59f82737-841f-5012-8da5-0bfe909bba19", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.534984, - "y": 235.803778 - }, - "endPoint": { - "x": 380.500575, - "y": 225.9734 - } - } - } - ] - }, - { - "id": "74b9bbbe-df6e-5ce3-9239-e3209870598f", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "ab249891-3e19-5147-a841-5d2b0866c0d7", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 377.223668, - "y": 233.504219 - } - } - } - ] - }, - { - "id": "dcb8b38d-6185-575f-b4ee-b00afdc304ec", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "f0fc86dd-c655-58c5-aa78-83bf25f535b2", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 379.437652, - "y": 236.794682 - } - } - } - ] - }, - { - "id": "7697866f-d339-58db-96f9-0088337ac6cc", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "96083634-c390-59fe-b433-38f85c3ed1d0", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.605189, - "y": 235.08904 - }, - "endPoint": { - "x": 377.223668, - "y": 233.504219 - } - } - }, - { - "id": "eac5b79b-02d1-5d5b-969f-4baea7bad673", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 377.223668, - "y": 233.504219 - }, - "endPoint": { - "x": 378.866249, - "y": 235.945452 - } - } - } - ] - }, - { - "id": "c1dfcf25-62e9-538c-8f5d-66ad9e1540ac", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "691a2869-06a4-572f-8bfc-98047ab1bd48", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.530668, - "y": 235.847717 - }, - "endPoint": { - "x": 379.534984, - "y": 235.803778 - } - } - } - ] - }, - { - "id": "4777d036-1245-5d4d-8ea7-8230286c9255", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "defa3147-faae-506b-9d69-dc7e7efb7157", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 383.815842, - "y": 233.48741 - } - } - } - ] - }, - { - "id": "e2519a98-ef75-55b5-90b5-e755ed30f1d0", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "63cea5cf-a790-57b1-8ed2-a5bf1218846a", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 382.18573, - "y": 235.936989 - }, - "endPoint": { - "x": 383.815842, - "y": 233.48741 - } - } - }, - { - "id": "39e81350-6708-5b6c-9207-1a8d50311de2", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 383.815842, - "y": 233.48741 - }, - "endPoint": { - "x": 381.442433, - "y": 235.084356 - } - } - } - ] - }, - { - "id": "97914abe-6b82-53a4-b222-ee1cb1af8ef2", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "75f3d793-39e8-5654-a1e1-aef11e877388", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 380.525378, - "y": 235.701395 - } - } - } - ] - }, - { - "id": "3e6e8323-45fe-596b-8fac-0c161ec75934", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "416d5122-af17-5c40-bad6-bc536a7f984f", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.516282, - "y": 235.798727 - }, - "endPoint": { - "x": 381.520822, - "y": 235.842642 - } - } - } - ] - }, - { - "id": "14e440df-1743-5335-9ee6-83f33dc0fefa", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "53584a43-8c24-5d46-94f1-d03bf48af315", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.528159, - "y": 236.791902 - }, - "endPoint": { - "x": 381.618665, - "y": 236.789121 - } - } - } - ] - }, - { - "id": "b3787d17-5449-5a53-9abe-fb8305433759", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "5bc7a805-e564-4e10-a5c6-08770dee7374", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.567469, - "y": 252.209497 - }, - "endPoint": { - "x": 380.106447, - "y": 250.393027 - } - } - }, - { - "id": "65715537-5650-43e5-aef7-784c3c16621b", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.106447, - "y": 250.393027 - }, - "endPoint": { - "x": 380.5619857896512, - "y": 249.92135242256356 - } - } - } - ] - }, - { - "id": "02b36675-7c0c-551f-8df6-2264393e0c5a", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "910e40a6-09c8-5733-92f0-0e4bf9423a19", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.555743, - "y": 247.610403 - }, - "endPoint": { - "x": 381.525649, - "y": 237.736086 - } - } - } - ] - }, - { - "id": "16c615e2-b657-5aa6-b91f-bfc7e6585df4", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "6638b938-a363-5c05-a9df-0b46bfb97dd3", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.547401, - "y": 244.338883 - }, - "endPoint": { - "x": 380.555743, - "y": 247.610403 - } - } - }, - { - "id": "528740e4-ad57-5d7c-9fe8-240f65378a94", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 380.555743, - "y": 247.610403 - }, - "endPoint": { - "x": 379.540036, - "y": 237.785076 - } - } - } - ] - }, - { - "id": "48e5cea5-af62-5b8a-a4c5-ff006321b856", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "778ba35b-051d-5e65-b587-f0be9103061d", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 378.870587, - "y": 237.646814 - }, - "endPoint": { - "x": 377.240476, - "y": 240.096393 - } - } - }, - { - "id": "894702be-b383-5dea-87d6-3cc27bff6583", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 377.240476, - "y": 240.096393 - }, - "endPoint": { - "x": 379.613885, - "y": 238.499447 - } - } - } - ] - }, - { - "id": "345495ee-1586-525d-847c-b7374e38df95", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "016e7bd7-3f6b-53f6-af66-b1fecddba3f1", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.577761, - "y": 237.747158 - }, - "endPoint": { - "x": 379.535496, - "y": 237.741161 - } - } - } - ] - }, - { - "id": "01a8f807-b222-5fb1-999e-b6b0f014b5f4", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "02b029f3-380b-5157-8cc7-84d4c4615452", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 379.540036, - "y": 237.785076 - }, - "endPoint": { - "x": 379.535496, - "y": 237.741161 - } - } - } - ] - }, - { - "id": "c3f8ba49-82a1-5c42-b62e-98cacc8de059", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "ab907944-26b1-5be9-a8fd-7cd38509c27b", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.525649, - "y": 237.736086 - }, - "endPoint": { - "x": 381.483415, - "y": 237.742299 - } - } - } - ] - }, - { - "id": "ff01dc0d-6daa-55b2-823d-33d9571c8040", - "type": "PolyLine", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": null, - "children": [ - { - "id": "62edb0f7-dce4-5d20-90b7-be4416de28be", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 381.451128, - "y": 238.494763 - }, - "endPoint": { - "x": 383.83265, - "y": 240.079585 - } - } - }, - { - "id": "b13141d0-b9ef-51ca-8311-37576e4a85d9", - "type": "Line", - "lineColor": "#a55200", - "lineWidth": 1, - "layerId": "-00.기본BOX", - "shapeData": { - "startPoint": { - "x": 383.83265, - "y": 240.079585 - }, - "endPoint": { - "x": 382.190068, - "y": 237.638351 - } - } - } - ] - }, - { - "id": "b5c4fdd4-1e30-5a46-9ab3-8c7bbf72898e", - "type": "Text", - "lineColor": "#a55200", - "lineWidth": 1, - "lineDash": [], - "layerId": "-00.기본BOX", - "shapeData": { - "label": "N", - "basePoint": { - "x": 380.569612, - "y": 254.33105 - }, - "options": { - "textDirection": { - "x": 1, - "y": 0 - }, - "textAlign": "center", - "textColor": "#a55200", - "fontSize": 2.2589, - "fontFamily": "sans-serif" - } + "id": "ee08a256-9970-52ff-a1d6-7ced755193a7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 383.83265, + "y": 240.079585 + }, + "endPoint": { + "x": 381.521334, + "y": 237.780025 } - } - ], - "layers": [ - { - "id": "-00.기본BOX TEXT", - "name": "-00.기본BOX TEXT", - "isVisible": true, - "isLocked": false + } }, { - "id": "-00.기본BOX", - "name": "-00.기본BOX", - "isVisible": true, - "isLocked": false + "id": "aa55c03a-61c0-5d4c-a10e-b93022d0dbef", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.521334, + "y": 237.780025 + }, + "endPoint": { + "x": 381.525649, + "y": 237.736086 + } + } + }, + { + "id": "2b52c32c-1cec-5e01-8e42-e9d58c0083ff", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.525649, + "y": 237.736086 + }, + "endPoint": { + "x": 382.190068, + "y": 237.638351 + } + } + }, + { + "id": "c8e434ac-6de7-5b23-a967-8dfbf12b2def", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 382.190068, + "y": 237.638351 + }, + "endPoint": { + "x": 383.83265, + "y": 240.079585 + } + } } - ], - "drawing_area": [ - 42, - 47, - 812, - 567 - ] + ] + }, + { + "id": "52ae915b-884f-5076-972e-e2d3df4361da", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "0175fa80-3e29-553d-a1d5-285691a1b1a7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.516282, + "y": 235.798727 + }, + "endPoint": { + "x": 381.442433, + "y": 235.084356 + } + } + }, + { + "id": "91d94049-3051-5c55-8dfa-00f01c5ab89c", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.442433, + "y": 235.084356 + }, + "endPoint": { + "x": 383.815842, + "y": 233.48741 + } + } + }, + { + "id": "a44dbece-6ab7-58b1-8c6c-b4e48a89d706", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 383.815842, + "y": 233.48741 + }, + "endPoint": { + "x": 381.516282, + "y": 235.798727 + } + } + } + ] + }, + { + "id": "298f9cde-40ee-5f14-90b1-997ae4a1a959", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "46e587a7-6efa-592f-a5c0-399eed33b542", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.534984, + "y": 235.803778 + }, + "endPoint": { + "x": 379.530668, + "y": 235.847717 + } + } + }, + { + "id": "32a2c473-b3b8-56a4-a698-b64d4f27f018", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.530668, + "y": 235.847717 + }, + "endPoint": { + "x": 378.866249, + "y": 235.945452 + } + } + }, + { + "id": "065e534b-ec62-50a6-aff1-1a2ee31adde1", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 378.866249, + "y": 235.945452 + }, + "endPoint": { + "x": 377.223668, + "y": 233.504219 + } + } + }, + { + "id": "b904116a-5486-51b5-8623-5f191f05e5fa", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 377.223668, + "y": 233.504219 + }, + "endPoint": { + "x": 379.534984, + "y": 235.803778 + } + } + } + ] + }, + { + "id": "b552ef8f-e8fd-53f7-8b52-16fc785bae44", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "7d6abbf4-688a-592c-ac2b-e340ec32a649", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.540036, + "y": 237.785076 + }, + "endPoint": { + "x": 379.613885, + "y": 238.499447 + } + } + }, + { + "id": "8e174be7-d37f-50c3-a1b4-fc5e9db3c7ba", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.613885, + "y": 238.499447 + }, + "endPoint": { + "x": 377.240476, + "y": 240.096393 + } + } + }, + { + "id": "28342a6e-4ea2-5e55-ab34-0c41a9127378", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 377.240476, + "y": 240.096393 + }, + "endPoint": { + "x": 379.540036, + "y": 237.785076 + } + } + } + ] + }, + { + "id": "407f4a25-36f3-50ba-9836-80e859bcf9f8", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "56785032-420c-5ce4-9171-237469dc69e7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 379.534984, + "y": 235.803778 + } + } + }, + { + "id": "52171c83-b13b-5d4f-96d2-619312c5b8fe", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.534984, + "y": 235.803778 + }, + "endPoint": { + "x": 380.500575, + "y": 225.9734 + } + } + }, + { + "id": "e0fb6280-d368-5346-a923-5325c041240a", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.500575, + "y": 225.9734 + }, + "endPoint": { + "x": 380.508916, + "y": 229.24492 + } + } + }, + { + "id": "b799031c-9cc1-527c-a3b6-568d7043b09b", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.508916, + "y": 229.24492 + }, + "endPoint": { + "x": 380.528159, + "y": 236.791902 + } + } + } + ] + }, + { + "id": "36439b23-1504-59b3-932f-fe3bc2042d61", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "4b8bbad8-2ca4-5cd9-9b7f-b6a567e29194", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 372.981177, + "y": 236.811144 + }, + "endPoint": { + "x": 380.528159, + "y": 236.791902 + } + } + }, + { + "id": "6461eddb-65ab-5081-953a-3e479e07f115", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 379.577761, + "y": 237.747158 + } + } + }, + { + "id": "c997d76e-da7d-5d58-adfd-14f83abaca18", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.577761, + "y": 237.747158 + }, + "endPoint": { + "x": 372.981177, + "y": 236.811144 + } + } + } + ] + }, + { + "id": "110e6249-d765-58a5-b513-83873e1a09dd", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "ae89c362-3f17-5bc3-8ca2-791f07f98a1a", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.555743, + "y": 247.610403 + }, + "endPoint": { + "x": 380.528159, + "y": 236.791902 + } + } + }, + { + "id": "98d64c6c-6856-5925-8739-37725b69595a", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 381.521334, + "y": 237.780025 + } + } + }, + { + "id": "f3db0b69-00fd-5ac2-86ce-91aab356a253", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.521334, + "y": 237.780025 + }, + "endPoint": { + "x": 380.555743, + "y": 247.610403 + } + } + } + ] + }, + { + "id": "0b231456-c8ff-5c78-8df3-65c06e9fad88", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "df1661e1-c643-58e8-8025-1a200e7c7efa", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 388.075141, + "y": 236.772659 + }, + "endPoint": { + "x": 380.528159, + "y": 236.791902 + } + } + }, + { + "id": "69f3977d-840d-568a-ad7b-fad69dfb8928", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 381.478556, + "y": 235.836645 + } + } + }, + { + "id": "dfa3951a-ed6b-54c0-af72-1729b2f122f3", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.478556, + "y": 235.836645 + }, + "endPoint": { + "x": 388.075141, + "y": 236.772659 + } + } + } + ] + }, + { + "id": "bf5d42a0-72e6-5e69-9aeb-9536d999e2d8", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "a8820faf-4457-52a4-a077-cdf88af46662", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.567469, + "y": 252.209497 + }, + "endPoint": { + "x": 380.493656, + "y": 221.40732 + } + } + } + ] + }, + { + "id": "5f5337b1-d9af-5371-80bb-0b82c9b0c982", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "a37ca433-5694-569d-b058-babbbebb1e9d", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.577761, + "y": 237.747158 + }, + "endPoint": { + "x": 372.981177, + "y": 236.811144 + } + } + }, + { + "id": "2222c71d-84e0-502f-ad1c-9df2bc35358c", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 372.981177, + "y": 236.811144 + }, + "endPoint": { + "x": 379.572902, + "y": 235.841504 + } + } + } + ] + }, + { + "id": "68aebd36-2bcf-5651-affe-643fe976079f", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "b4f79eda-8381-5dea-93f1-7ef1934b34bd", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 377.240476, + "y": 240.096393 + } + } + } + ] + }, + { + "id": "f67ff1b1-4ef6-565b-9f59-554a2fb76b87", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "879b8834-d18e-51a2-817e-44b03837bc73", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 380.530939, + "y": 237.882408 + } + } + } + ] + }, + { + "id": "e412b991-a74c-5a7b-9912-58777bc381dd", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "73ff73da-587c-5825-979c-24a2bfe792ba", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.478556, + "y": 235.836645 + }, + "endPoint": { + "x": 388.075141, + "y": 236.772659 + } + } + }, + { + "id": "66e9e781-4aa9-5af6-9ea3-e9c8231c51bb", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 388.075141, + "y": 236.772659 + }, + "endPoint": { + "x": 381.483415, + "y": 237.742299 + } + } + } + ] + }, + { + "id": "0db65160-baf3-5377-a0df-4dbefff6efc1", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "330f9c78-f00d-51f6-9203-56368424d91d", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 383.83265, + "y": 240.079585 + } + } + } + ] + }, + { + "id": "0b507838-c0a0-53fa-8091-cb516a60f62d", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "6e65c446-e85b-5272-8a3d-d9cb162aefc6", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 370.346907, + "y": 236.82218 + }, + "endPoint": { + "x": 390.820396, + "y": 236.76134 + } + } + } + ] + }, + { + "id": "78eb244d-8bb5-5117-a14e-a79adc1b9799", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "cd29b354-0165-5e86-90c1-e3435bcb8da7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.508916, + "y": 229.24492 + }, + "endPoint": { + "x": 380.500575, + "y": 225.9734 + } + } + }, + { + "id": "6695f801-d4a6-5255-bc57-90f74ec46ffa", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.500575, + "y": 225.9734 + }, + "endPoint": { + "x": 381.516282, + "y": 235.798727 + } + } + } + ] + }, + { + "id": "1c9d17c7-109f-52c5-a106-124b9d8bb6ea", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "59f82737-841f-5012-8da5-0bfe909bba19", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.534984, + "y": 235.803778 + }, + "endPoint": { + "x": 380.500575, + "y": 225.9734 + } + } + } + ] + }, + { + "id": "74b9bbbe-df6e-5ce3-9239-e3209870598f", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "ab249891-3e19-5147-a841-5d2b0866c0d7", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 377.223668, + "y": 233.504219 + } + } + } + ] + }, + { + "id": "dcb8b38d-6185-575f-b4ee-b00afdc304ec", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "f0fc86dd-c655-58c5-aa78-83bf25f535b2", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 379.437652, + "y": 236.794682 + } + } + } + ] + }, + { + "id": "7697866f-d339-58db-96f9-0088337ac6cc", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "96083634-c390-59fe-b433-38f85c3ed1d0", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.605189, + "y": 235.08904 + }, + "endPoint": { + "x": 377.223668, + "y": 233.504219 + } + } + }, + { + "id": "eac5b79b-02d1-5d5b-969f-4baea7bad673", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 377.223668, + "y": 233.504219 + }, + "endPoint": { + "x": 378.866249, + "y": 235.945452 + } + } + } + ] + }, + { + "id": "c1dfcf25-62e9-538c-8f5d-66ad9e1540ac", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "691a2869-06a4-572f-8bfc-98047ab1bd48", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.530668, + "y": 235.847717 + }, + "endPoint": { + "x": 379.534984, + "y": 235.803778 + } + } + } + ] + }, + { + "id": "4777d036-1245-5d4d-8ea7-8230286c9255", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "defa3147-faae-506b-9d69-dc7e7efb7157", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 383.815842, + "y": 233.48741 + } + } + } + ] + }, + { + "id": "e2519a98-ef75-55b5-90b5-e755ed30f1d0", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "63cea5cf-a790-57b1-8ed2-a5bf1218846a", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 382.18573, + "y": 235.936989 + }, + "endPoint": { + "x": 383.815842, + "y": 233.48741 + } + } + }, + { + "id": "39e81350-6708-5b6c-9207-1a8d50311de2", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 383.815842, + "y": 233.48741 + }, + "endPoint": { + "x": 381.442433, + "y": 235.084356 + } + } + } + ] + }, + { + "id": "97914abe-6b82-53a4-b222-ee1cb1af8ef2", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "75f3d793-39e8-5654-a1e1-aef11e877388", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 380.525378, + "y": 235.701395 + } + } + } + ] + }, + { + "id": "3e6e8323-45fe-596b-8fac-0c161ec75934", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "416d5122-af17-5c40-bad6-bc536a7f984f", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.516282, + "y": 235.798727 + }, + "endPoint": { + "x": 381.520822, + "y": 235.842642 + } + } + } + ] + }, + { + "id": "14e440df-1743-5335-9ee6-83f33dc0fefa", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "53584a43-8c24-5d46-94f1-d03bf48af315", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.528159, + "y": 236.791902 + }, + "endPoint": { + "x": 381.618665, + "y": 236.789121 + } + } + } + ] + }, + { + "id": "b3787d17-5449-5a53-9abe-fb8305433759", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "1d3f850f-597e-5adb-9d9e-d39941552a47", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.567469, + "y": 252.209497 + }, + "endPoint": { + "x": 380.106447, + "y": 250.393027 + } + } + }, + { + "id": "06fca6d7-469f-5498-a346-51fde73e5506", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.106447, + "y": 250.393027 + }, + "endPoint": { + "x": 381.007941, + "y": 249.915905 + } + } + } + ] + }, + { + "id": "02b36675-7c0c-551f-8df6-2264393e0c5a", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "910e40a6-09c8-5733-92f0-0e4bf9423a19", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.555743, + "y": 247.610403 + }, + "endPoint": { + "x": 381.525649, + "y": 237.736086 + } + } + } + ] + }, + { + "id": "16c615e2-b657-5aa6-b91f-bfc7e6585df4", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "6638b938-a363-5c05-a9df-0b46bfb97dd3", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.547401, + "y": 244.338883 + }, + "endPoint": { + "x": 380.555743, + "y": 247.610403 + } + } + }, + { + "id": "528740e4-ad57-5d7c-9fe8-240f65378a94", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 380.555743, + "y": 247.610403 + }, + "endPoint": { + "x": 379.540036, + "y": 237.785076 + } + } + } + ] + }, + { + "id": "48e5cea5-af62-5b8a-a4c5-ff006321b856", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "778ba35b-051d-5e65-b587-f0be9103061d", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 378.870587, + "y": 237.646814 + }, + "endPoint": { + "x": 377.240476, + "y": 240.096393 + } + } + }, + { + "id": "894702be-b383-5dea-87d6-3cc27bff6583", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 377.240476, + "y": 240.096393 + }, + "endPoint": { + "x": 379.613885, + "y": 238.499447 + } + } + } + ] + }, + { + "id": "345495ee-1586-525d-847c-b7374e38df95", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "016e7bd7-3f6b-53f6-af66-b1fecddba3f1", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.577761, + "y": 237.747158 + }, + "endPoint": { + "x": 379.535496, + "y": 237.741161 + } + } + } + ] + }, + { + "id": "01a8f807-b222-5fb1-999e-b6b0f014b5f4", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "02b029f3-380b-5157-8cc7-84d4c4615452", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 379.540036, + "y": 237.785076 + }, + "endPoint": { + "x": 379.535496, + "y": 237.741161 + } + } + } + ] + }, + { + "id": "c3f8ba49-82a1-5c42-b62e-98cacc8de059", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "ab907944-26b1-5be9-a8fd-7cd38509c27b", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.525649, + "y": 237.736086 + }, + "endPoint": { + "x": 381.483415, + "y": 237.742299 + } + } + } + ] + }, + { + "id": "ff01dc0d-6daa-55b2-823d-33d9571c8040", + "type": "PolyLine", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": null, + "children": [ + { + "id": "62edb0f7-dce4-5d20-90b7-be4416de28be", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 381.451128, + "y": 238.494763 + }, + "endPoint": { + "x": 383.83265, + "y": 240.079585 + } + } + }, + { + "id": "b13141d0-b9ef-51ca-8311-37576e4a85d9", + "type": "Line", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "startPoint": { + "x": 383.83265, + "y": 240.079585 + }, + "endPoint": { + "x": 382.190068, + "y": 237.638351 + } + } + } + ] + }, + { + "id": "b5c4fdd4-1e30-5a46-9ab3-8c7bbf72898e", + "type": "Text", + "lineColor": "#a55200", + "lineWidth": 1, + "layerId": "-00.기본BOX", + "shapeData": { + "label": "N", + "basePoint": { + "x": 380.569612, + "y": 254.33105 + }, + "options": { + "textDirection": { + "x": 1.0, + "y": 0.0 + }, + "textAlign": "center", + "textColor": "#a55200", + "fontSize": 2.2589, + "fontFamily": "sans-serif" + } + } + } + ], + "layers": [ + { + "id": "-00.기본BOX TEXT", + "name": "-00.기본BOX TEXT", + "isVisible": true, + "isLocked": false + }, + { + "id": "-00.기본BOX", + "name": "-00.기본BOX", + "isVisible": true, + "isLocked": false + } + ] } From 6a5c5382b356656cf2c33fd41031b5d42076c7ab Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 30 Sep 2026 17:39:17 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore(M01):=20=EC=82=B0=EB=A6=BC=201~3?= =?UTF-8?q?=EC=9E=A5=20=ED=91=9C=2099=EA=B0=9C=20=EB=8C=80=EC=A1=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D(PLAN=2029-4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KYNJBegcapQYMwkKAf8D7S --- docs/raw/M01_점검/sub1_3/보고.md | 93 +++++++++++++++++++ docs/raw/M01_점검/sub1_3/보고_줄글.md | 27 ++++++ docs/raw/M01_점검/sub1_3/진행.md | 126 ++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 docs/raw/M01_점검/sub1_3/보고.md create mode 100644 docs/raw/M01_점검/sub1_3/보고_줄글.md create mode 100644 docs/raw/M01_점검/sub1_3/진행.md diff --git a/docs/raw/M01_점검/sub1_3/보고.md b/docs/raw/M01_점검/sub1_3/보고.md new file mode 100644 index 00000000..66b06911 --- /dev/null +++ b/docs/raw/M01_점검/sub1_3/보고.md @@ -0,0 +1,93 @@ +# M01 원문 ↔ 자료 표 대조 보고 · sub1_3 (산림 1장 59표 + 2장 24표 + 3장 16표 = 99표) + +점검만 · 코드 · 로직 · 자료 수정 없음 · 기록은 `tmp/M01_점검/sub1_3/` (진행.md · 보고_줄글.md · 이 파일 · 도구 `tools/`) · 방식 = `docs/raw/M01_점검/대조방식.md`. + +## 1. 결과 한눈에 + +- 범위 99표 전부 판정(미판정 0). 표 키 99/99 가 자료 파일 셋(계수_산림품셈_01장 59 · 소요량_산림품셈_02장 24 · 소요량_산림품셈_03장 16)과 진행.md 에서 빠짐 · 겹침 없이 일치. +- **표값(칸 값)**: 끝남 99 · 흠 0. 「끝남」 99줄 전부 근거 칸에 `N/N · 원문 파일 · 절` 있음(N 이 서로 같음 — 어긋난 칸 0). 원문의 숫자 · 단위 · 병합 칸 · 원문 표기(괄호 · 「6-7」 · 「〃」 등)가 자료와 같음. +- **흠·의심 23줄**(확실 11 · 의심 12) — 표 칸 값의 흠은 0 · 흠은 표 형식(범위규칙 글 · 조건 겹침) 8표 · 연결 표시 2표 · 로직 쪽 13표(2장 소요재료를 로직이 안 씀 · 3-2 로직 없음 등). +- **엔진 찾기 시험**: 99표 줄 전부를 찾기로 불러 봄 → 못 찾는 표 8(범위규칙 글 5 · 정수 조건 1 · 줄 겹침 2). 값 칸이 범위인 참고표 7(CF000004 · 008 · 010 · 012 · 014 · 017 · 059)은 「입력으로 받는 값」 이라 찾기 대상 아님. +- **로직**: 맞음 22 · 흠/의심 15 · 미사용 35 · 해당 없음 27. 1장 1-4 할증표(CF000027~056)는 쓰는 로직이 거의 없음(CF000038 · 043 · 052 · 054 만) — 28-2a 새 로직 비고에 「할인·할증 못 넣음」 이라 적힌 것과 같은 이유(찾기 못하는 표가 일부). + +| 부문·장 | 표 | 끝남 | 확실(표) | 의심(표) | 찾기 못함 | 로직 맞음 | 로직 흠·의심 | 미사용 | 해당 없음 | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| 산림 1장 적용기준 | 59 | 59 | 5 | 3 | 7 | 4 | 0 | 33 | 22 | +| 산림 2장 소요재료 및 기계손료 | 24 | 24 | 5 | 8 | 1 | 5 | 13 | 2 | 4 | +| 산림 3장 작업장 관리 | 16 | 16 | 1 | 1 | 0 | 13 | 2 | 0 | 1 | +| 합계 | 99 | 99 | 11 | 12 | 8 | 22 | 15 | 35 | 27 | + +## 2. 대조 방법 (사실) + +- **점검원**: 하위 점검원 없음 — sub1 이 99표를 혼자 처음부터 끝까지 대조(재확인은 아래 4장 · 같은 사람이 다른 길로 다시 돌림). +- **원문**: `resources/knowledge/original/원가계산/산림_표준품셈/본문/` 제01장(1-01~1-07) · 제02장(2-01 · 2-02) · 제03장(3-01~3-08) md 열다섯 파일. 표마다 md 표와 그 표 앞뒤 [주] · 예시 글까지 같이 읽음. +- **칸 대조(눈)**: `tools/side.py` 로 원문 md 와 자료 표 줄을 나란히 찍어 줄마다 · 칸마다 눈으로 맞댐 — 병합 칸(CF000025 · 031 · 043 · 046 · 057 · QF000027 · 032 등)은 위 줄에서 이어 읽어 자료가 복사한 값과 맞춤 · 큰 표는 줄 하나씩(CF000006 73줄 · CF000020 50줄 · QF000038 30칸 · CF000012 17줄). +- **스크립트 보조 4가지**: `tools/numdiff.py`(절별 원문 표 수 ↔ 자료 표 수 다중집합 비교 — 어긋남은 부호 「−」 · 머리 글 수 · 주 글 속 수뿐 · 표 칸 수 어긋남 0) · `tools/notes.py`(자료 표 [주] 161줄이 원문에 그대로 있는지 — 없는 17줄은 병합 · 요약 문장이라 눈으로 수 · 뜻 확인, 값 다름 0) · `tools/lookup_try.py`(표 99개 줄 전부를 엔진 찾기로 불러 보기) · `tools/reread.py`(재확인 두 번째 길). +- **로직 쪽**: 표를 쓰는 산림 로직 46개(GF000001~012 · 017 · 018 · 020 · 051 · 052 · 054~056 · 061~070 · 073~079 · 081 · 083 · 084 · 308 · 309 · 312~315)의 식에서 표를 부르는 줄을 읽고 표 · 원문과 맞춤(3장 GF000001~010 은 로직 전체) · GF000002(작업로 설치) 계산도 돌려 휘발유 · 잡품 · 오일 · 손료 금액을 손으로 맞춤. + +## 3. 흠 · 의심 목록 + +한 줄 = 한 흠 · 자세한 문장은 `보고_줄글.md`. 판정: 표 칸 값이 원문과 다른 흠은 없음 · 아래는 표 형식 · 연결 표시 · 로직 쪽. + +| 판정 | 키 | 이름 | 절 | 갈래 | 한 줄 | +|---|---|---|---|---|---| +| 확실 | CF000027 | 품의 할증 — 작업시기 | 1-4-1 | 기타(표 형식) | 범위규칙이 「작업시기(월) 이상·이하 — …」 글이라 엔진이 「모르는 범위규칙」 으로 멈춤 — 6~9월 등 월 구간을 찾기로 못 부름(값 3줄 자체는 원문과 같음) | +| 확실 | CF000029 | 품의 할증 — 집단화정도 | 1-4-3 | 기타(표 형식) | 범위규칙이 「개소당 평균면적(ha) 이상·미만 — …」 글이라 「모르는 범위규칙」 으로 멈춤 — 1ha · 3ha 구간 찾기 불가(값 3줄은 원문과 같음) | +| 확실 | CF000034 | 품의 할증 — 작업장까지의 이동거리 | 1-4-6 | 기타(표 형식) | 범위규칙이 「이동거리(㎞) 이상·미만 — …」 글이라 「모르는 범위규칙」 으로 멈춤 — 1.3㎞ · 2.5㎞ 구간 찾기 불가(값 6줄은 원문과 같음) | +| 확실 | CF000044 | 품의 할증 — 횡단운반거리 | 1-4-15 | 기타(표 형식) | 범위규칙이 「횡단운반거리(m) 이상·이하 — …」 글이라 「모르는 범위규칙」 으로 멈춤 — 0~5 · 6~10 · 11~20m 구간 찾기 불가(값 3줄은 원문과 같음) | +| 확실 | QF000009 | 소금처리(본당) | 2-1-5-2 | 기타(표 형식) | 범위규칙 「㎝미만·㎝이상 표기대로」 를 엔진이 모름 — 주두부직경 구간 찾기 불가(값 4줄은 원문과 같음) · 그래서 GF000304 도 못 씀 | +| 확실 | QF000017 | 드론 무인 헬리콥터(ha당) | 2-1-9 | 로직 일부 구현 | 원문 표의 휘발유 1.13ℓ/ha · 잡품 주재료비의 45% 가 있는데 GF000320(8-6-2)에 연료 줄이 없음 — 비고에 「연료 원문 표에 없음」 이라 적었으나 2-1-9 에 있음(휘발유 단가는 자료에 있음) | +| 확실 | QF000019 | 드론 무인 멀티콥터(ha당) | 2-1-10 | 로직 일부 구현 | 원문 표의 휘발유(발전기) 0.81ℓ/ha · 잡품 30% 가 있는데 GF000320 멀티콥터 쪽에 연료 줄이 없음 | +| 확실 | QF000021 | 지상 약제살포 | 2-1-11 | 로직 일부 구현 | 원문 표의 경유(차량살포) 1.3ℓ/ha(15ha 20ℓ) · 잡품 5% 가 있는데 GF000321(8-6-3)에 연료 줄이 없음(GF000321 은 원/일 — ha 환산 필요) | +| 확실 | QF000026 | 작업로 예정선 선정 및 표식 | 3-2 | 로직 없음(절 누락) | 초급기술자 1.0인/1일 1km 표가 있는데 3-2 로직이 없음(3장 로직은 GF000001~010 — 3-2 없음) · 초급기술자 노임 LB000293 은 인력에 있음 · 표 값은 원문과 같음 | +| 확실 | CF000043 | 품의 할증 — 집재방향 | 1-4-14 | 기타(연결 표시) | 표의 로직 목록에 실제로 쓰는 GF000309 · GF000312 · GF000313(28-2a 새 로직)이 빠짐 | +| 확실 | QF000002 | 경유 사용 임업기계 연료 | 2-1-2 | 기타(연결 표시) | 표의 로직 목록에 실제로 쓰는 GF000308 · 309 · 312 · 313 · 314 · 315(28-2a 새 로직)가 빠짐 | +| 의심 | CF000055 | 품의 할증 — 작업시간 제한 | 1-4-25 | 기타(표 형식) | 원문은 「2시간 이하 … 6시간 이하」 구간인데 조건이 「수」 정확값 — 2.5시간 · 4.5시간을 넣으면 「줄이 없음」(엔진 「이하」 범위규칙이면 해결) | +| 의심 | CF000006 | 설계서의 단위 및 소수의 표준 | 1-2-2 | 기타(표 형식) | 조건 칸이 종목 하나뿐이라 같은 종목 여러 줄(견치돌 · 돌쌓기 · 목재 · 말뚝 · 돌망태 · 관류 16줄)이 겹쳐 찾기가 「줄이 2개」 로 멈춤 — 참고표라 쓰는 로직 없음 | +| 의심 | CF000020 | 화물자동차의 적재량 | 1-2-7 | 기타(표 형식) | 조건 칸(종별 · 규격)만으로 안 갈리는 줄(블록 · 페인트 · 흄관 · 콘크리트관 · 주철관 · 도복장강관 · P·C 파일 · 전주 12줄)이 겹쳐 「줄이 2개」 로 멈춤 — 쓰는 로직 없음 | +| 의심 | QF000008 | 덩굴 피복도별 농약 소요량 증감률 | 2-1-5-1 | 로직 일부 구현 | 원문 예시 두 줄(밀 +20 · 과소 −50)만 수록 — 원문 주②는 덩굴 피복도(1-4-11 5등급: 과밀 +50 · 소 −20 포함)에 따라 가감이라 GF000055 가 과밀 · 소를 못 씀 | +| 의심 | QF000028 | 임산물 운반로 신설비 산정(예시) | 3-4-2 | 계산식 어긋남 | GF000003 이 Q 를 3자리(8.946 · 공통 8-1-4 「소수점이하 3자리」 규칙)로 반올림하나 산림 원문 예시는 8.95(2자리) — 결과 0.05% 차이 · 어느 쪽 따를지 사람 확인 | +| 의심 | QF000005 | 페인트 및 마킹테이프 재료비 | 2-1-4 | 로직 일부 구현 | 경계표시(3-1) 페인트 0.2ℓ/ha · 벌도 위험목 점검(4-5) 0.1ℓ/ha · 작업로 선정 페인트 · 마킹테이프가 있는데 GF000001 · GF000014 에 재료 줄이 없고 3-2 는 로직 없음(수성페인트는 자재품목에 있음 · 마킹테이프는 없음) | +| 의심 | QF000010 | 소금처리 잡품율 | 2-1-5-2 | 로직 일부 구현 | GF000304(6-4-3)에 소금(본당 g) · 잡품 10% 줄이 없음 — 소금은 자재품목에 없음(비고에 「소금 원문 표에 없음」 이라 적었으나 2-1-5-2 에 있음) | +| 의심 | QF000011 | 뿌리제거(100본당) | 2-1-5-3 | 로직 일부 구현 | GF000305(6-4-4)에 농약(글리포세이트 1.5병/100본) · 친환경 비닐랩 300매/100본 · 잡품 66% 줄이 없음(글리포세이트 · 비닐랩은 자재품목에 없음) | +| 의심 | QF000012 | 나무주사(100본당) | 2-1-6 | 로직 일부 구현 | GF000317 · 318(8-1)에 천공테이프 · 페인트 · 표식라벨 · 천공기날 등 재료 줄이 없음(라벨 · 천공기날은 자재품목에 없음) — 2-1-1 천공기 휘발유 3ℓ/일 · 잡품 95% · 손료(EQ000643)도 안 씀 | +| 의심 | QF000014 | 소각, 매몰, 훈증, 박피 | 2-1-7 | 로직 일부 구현 | 훈증약제 · 피복제 · 표식라벨 · 벌근훈증약제 5ℓ · 벌근피복제 100개 · 약종별 약제량이 GF000078 · 079(8-3) · GF000083(8-8-1)에 없음(메탐소듐 액제 · 디메틸디설파이드는 자재품목에 없음) | +| 의심 | QF000023 | 파쇄 소모품 | 2-1-13 | 로직 일부 구현 | 메인파쇄기날 0.00125개/hr · 분쇄기날 0.005개/hr · 잡품 5% 가 GF000323(8-11)에 없음(파쇄기날 자재품목에 없음) | +| 의심 | QF000003 | 우드그래플 소모성 경비 | 2-1-3 | 로직 일부 구현 | 원문은 「산지에서 우드그랩이 이용되는 품에 적용」 인데 GF000069 만 소모성경비를 더하고 같은 우드그래플 품 GF000070 · 314 · 315 는 안 더함 | + +## 4. 재확인 (10표에 1표) + +재확인 10표 = CF000006 · 012 · 020 · 025 · 034 · 046 · 057 · QF000002 · 032 · 038. 원문 md 표를 줄 단위로 읽어 병합 칸을 채운 뒤 자료 표 줄과 「줄마다 수의 차례」 를 맞댐(`tools/reread.py`). + +| 표 | 원문 줄 | 자료 줄 | 어긋난 줄 | 비고 | +|---|---:|---:|---:|---| +| CF000006 | 73 | 73 | 0 | | +| CF000012 | 17 | 17 | 0 | | +| CF000020 | 50 | 50 | 0 | 시멘트 줄 1곳은 원문 칸 「25.5톤 화물차는 풀카고기준」 이 자료 「20톤 트레일러원문」 칸에 있어 수 차례에서 빠질 뿐(뜻 같음) | +| CF000025 | 17 | 17 | 0 | | +| CF000034 | 6 | 6 | 0 | | +| CF000046 | 9 | 9 | 0 | | +| CF000057 | 21(빈 줄 5 포함) | 16 | — | 원문 서식에 값 없는 줄(간접재료비 · 소계 · 총원가)이 있어 자동 맞댐이 안 됨 → 눈으로 16줄 재대조 · 어긋남 0 | +| QF000002 | 12 | 12 | 0 | | +| QF000032 | 9 | 9 | 0 | 소요인력 값 9개 같음 · 병합 칸(인력구분 50% · 50%)을 자료가 아홉 줄에 복사한 것은 정상 | +| QF000038 | 30칸 | 30 | 0 | | + +첫 대조(눈)와 두 번째 길(스크립트)이 모두 같음 — 재확인으로 뒤집힌 판정 0. + +## 5. 로직 쪽 참고 + +- **맞음 22표**: CF000038 · CF000043 · CF000052 · CF000054 · QF000001 · QF000002 · QF000004 · QF000007 · QF000016 · QF000025 · QF000027 · QF000029 · QF000030 · QF000031 · QF000032 · QF000033 · QF000034 · QF000035 · QF000036 · QF000037 · QF000038 · QF000039. +- **미사용 35표**: CF000012 · CF000015 · CF000016 · CF000020 · CF000021 · CF000022 · CF000023 · CF000024 · CF000025 · CF000026 · CF000027 · CF000028 · CF000029 · CF000031 · CF000033 · CF000034 · CF000035 · CF000036 · CF000037 · CF000039 · CF000040 · CF000042 · CF000044 · CF000045 · CF000046 · CF000047 · CF000048 · CF000049 · CF000051 · CF000053 · CF000055 · CF000056 · CF000059 · QF000015 · QF000024 — 값 표인데 쓰는 로직이 없음(1-4 할증표 · 재료할증 · 적재량 · 체적변화율 · 공제율 · 공사용수 · 2장 재료표 대부분 — 설계상 못 넣었거나 아직 안 넣음). +- **해당 없음 27표**: 정의 · 분류 · 표시 규칙 · 원가 서식 등 계산식 재료가 아닌 참고표. + +## 6. 범위 밖 참고 · 정정 + +- 2-2 기계손료 11절(체인톱 · 예취기 · 집재장비 20종 · 배부식분무기 · 천공기 · 양수기 · 드론 · 지상 약제살포 · 파쇄 · 기타장비)은 소요량 파일에 표가 없고 `기계.json`(EQ000620~654)에 있음. 손료계수 29 · 시간당 손료계수 1 · 장비가격 20 · 취득가액 3(53칸) 전부 원문과 같음을 확인 — 이 표 99개 밖이라 판정 줄에는 안 올림. +- 자료 표 [주] 문구 경미 차이 2건(값 영향 없음 · 흠 아님): QF000030 첫 [주] 「완전, 복구 경비」→「완전복구 경비」 · 둘째 [주] 「다음 조건에 따라」→「위 조건에 따라」. +- **정정(sub1 본인 기록)**: 28-2a 새 로직 비고 중 「재료 원문 표에 없음」 · 「연료 원문 표에 없음」 이라 적은 GF000304 · 305 · 317 · 318 · 320 · 321 · 323 은 2장 소요재료에 수량표가 있음(위 줄글). 자료는 이번에 안 고침. + +## 7. 사람 확인 · 남은 것 + +- QF000028 반올림 자리(공통 8-1-4 3자리 ↔ 산림 예시 2자리) · QF000008 과밀 · 소 등급을 어디에 둘지(2-1-5-1 표 늘리기 ↔ 1-4-11 을 로직이 직접 부르기) · 3-2 작업로 선정 로직을 새로 만들지. +- 범위규칙 글 표 5(CF000027 · 029 · 034 · 044 · QF000009) · 정수 조건 CF000055 는 표 고침(28-2d 계열) 뒤 로직을 넣을 수 있음. +- 2장 재료 줄(위 QF 흠 12줄)은 자재품목에 없는 품목(소금 · 마킹테이프 · 글리포세이트 · 비닐랩 · 표식라벨 · 천공기날 · 메탐소듐 · 파쇄기날)은 자재 자료를 넣거나 빈 줄로 남기는 결정이 필요. \ No newline at end of file diff --git a/docs/raw/M01_점검/sub1_3/보고_줄글.md b/docs/raw/M01_점검/sub1_3/보고_줄글.md new file mode 100644 index 00000000..199f3b8d --- /dev/null +++ b/docs/raw/M01_점검/sub1_3/보고_줄글.md @@ -0,0 +1,27 @@ +# M01 점검 결과 · 줄글 · sub1_3 (산림 1~3장) + +흠·의심 하나에 한 줄: `키 | 이름 | 절 | 갈래 | 설명 | 확실/의심`. 표 값(칸) 흠은 0 — 아래는 표 형식 · 연결 표시 · 로직 쪽. 점검만 · 자료 안 고침. + +CF000027 | 품의 할증 — 작업시기 | 1-4-1 | 기타(표 형식) | 범위규칙이 「작업시기(월) 이상·이하 — …」 글이라 엔진이 「모르는 범위규칙」 으로 멈춤 — 6~9월 등 월 구간을 찾기로 못 부름(값 3줄 자체는 원문과 같음) | 확실 +CF000029 | 품의 할증 — 집단화정도 | 1-4-3 | 기타(표 형식) | 범위규칙이 「개소당 평균면적(ha) 이상·미만 — …」 글이라 「모르는 범위규칙」 으로 멈춤 — 1ha · 3ha 구간 찾기 불가(값 3줄은 원문과 같음) | 확실 +CF000034 | 품의 할증 — 작업장까지의 이동거리 | 1-4-6 | 기타(표 형식) | 범위규칙이 「이동거리(㎞) 이상·미만 — …」 글이라 「모르는 범위규칙」 으로 멈춤 — 1.3㎞ · 2.5㎞ 구간 찾기 불가(값 6줄은 원문과 같음) | 확실 +CF000044 | 품의 할증 — 횡단운반거리 | 1-4-15 | 기타(표 형식) | 범위규칙이 「횡단운반거리(m) 이상·이하 — …」 글이라 「모르는 범위규칙」 으로 멈춤 — 0~5 · 6~10 · 11~20m 구간 찾기 불가(값 3줄은 원문과 같음) | 확실 +QF000009 | 소금처리(본당) | 2-1-5-2 | 기타(표 형식) | 범위규칙 「㎝미만·㎝이상 표기대로」 를 엔진이 모름 — 주두부직경 구간 찾기 불가(값 4줄은 원문과 같음) · 그래서 GF000304 도 못 씀 | 확실 +QF000017 | 드론 무인 헬리콥터(ha당) | 2-1-9 | 로직 일부 구현 | 원문 표의 휘발유 1.13ℓ/ha · 잡품 주재료비의 45% 가 있는데 GF000320(8-6-2)에 연료 줄이 없음 — 비고에 「연료 원문 표에 없음」 이라 적었으나 2-1-9 에 있음(휘발유 단가는 자료에 있음) | 확실 +QF000019 | 드론 무인 멀티콥터(ha당) | 2-1-10 | 로직 일부 구현 | 원문 표의 휘발유(발전기) 0.81ℓ/ha · 잡품 30% 가 있는데 GF000320 멀티콥터 쪽에 연료 줄이 없음 | 확실 +QF000021 | 지상 약제살포 | 2-1-11 | 로직 일부 구현 | 원문 표의 경유(차량살포) 1.3ℓ/ha(15ha 20ℓ) · 잡품 5% 가 있는데 GF000321(8-6-3)에 연료 줄이 없음(GF000321 은 원/일 — ha 환산 필요) | 확실 +QF000026 | 작업로 예정선 선정 및 표식 | 3-2 | 로직 없음(절 누락) | 초급기술자 1.0인/1일 1km 표가 있는데 3-2 로직이 없음(3장 로직은 GF000001~010 — 3-2 없음) · 초급기술자 노임 LB000293 은 인력에 있음 · 표 값은 원문과 같음 | 확실 +CF000043 | 품의 할증 — 집재방향 | 1-4-14 | 기타(연결 표시) | 표의 로직 목록에 실제로 쓰는 GF000309 · GF000312 · GF000313(28-2a 새 로직)이 빠짐 | 확실 +QF000002 | 경유 사용 임업기계 연료 | 2-1-2 | 기타(연결 표시) | 표의 로직 목록에 실제로 쓰는 GF000308 · 309 · 312 · 313 · 314 · 315(28-2a 새 로직)가 빠짐 | 확실 +CF000055 | 품의 할증 — 작업시간 제한 | 1-4-25 | 기타(표 형식) | 원문은 「2시간 이하 … 6시간 이하」 구간인데 조건이 「수」 정확값 — 2.5시간 · 4.5시간을 넣으면 「줄이 없음」(엔진 「이하」 범위규칙이면 해결) | 의심 +CF000006 | 설계서의 단위 및 소수의 표준 | 1-2-2 | 기타(표 형식) | 조건 칸이 종목 하나뿐이라 같은 종목 여러 줄(견치돌 · 돌쌓기 · 목재 · 말뚝 · 돌망태 · 관류 16줄)이 겹쳐 찾기가 「줄이 2개」 로 멈춤 — 참고표라 쓰는 로직 없음 | 의심 +CF000020 | 화물자동차의 적재량 | 1-2-7 | 기타(표 형식) | 조건 칸(종별 · 규격)만으로 안 갈리는 줄(블록 · 페인트 · 흄관 · 콘크리트관 · 주철관 · 도복장강관 · P·C 파일 · 전주 12줄)이 겹쳐 「줄이 2개」 로 멈춤 — 쓰는 로직 없음 | 의심 +QF000008 | 덩굴 피복도별 농약 소요량 증감률 | 2-1-5-1 | 로직 일부 구현 | 원문 예시 두 줄(밀 +20 · 과소 −50)만 수록 — 원문 주②는 덩굴 피복도(1-4-11 5등급: 과밀 +50 · 소 −20 포함)에 따라 가감이라 GF000055 가 과밀 · 소를 못 씀 | 의심 +QF000028 | 임산물 운반로 신설비 산정(예시) | 3-4-2 | 계산식 어긋남 | GF000003 이 Q 를 3자리(8.946 · 공통 8-1-4 「소수점이하 3자리」 규칙)로 반올림하나 산림 원문 예시는 8.95(2자리) — 결과 0.05% 차이 · 어느 쪽 따를지 사람 확인 | 의심 +QF000005 | 페인트 및 마킹테이프 재료비 | 2-1-4 | 로직 일부 구현 | 경계표시(3-1) 페인트 0.2ℓ/ha · 벌도 위험목 점검(4-5) 0.1ℓ/ha · 작업로 선정 페인트 · 마킹테이프가 있는데 GF000001 · GF000014 에 재료 줄이 없고 3-2 는 로직 없음(수성페인트는 자재품목에 있음 · 마킹테이프는 없음) | 의심 +QF000010 | 소금처리 잡품율 | 2-1-5-2 | 로직 일부 구현 | GF000304(6-4-3)에 소금(본당 g) · 잡품 10% 줄이 없음 — 소금은 자재품목에 없음(비고에 「소금 원문 표에 없음」 이라 적었으나 2-1-5-2 에 있음) | 의심 +QF000011 | 뿌리제거(100본당) | 2-1-5-3 | 로직 일부 구현 | GF000305(6-4-4)에 농약(글리포세이트 1.5병/100본) · 친환경 비닐랩 300매/100본 · 잡품 66% 줄이 없음(글리포세이트 · 비닐랩은 자재품목에 없음) | 의심 +QF000012 | 나무주사(100본당) | 2-1-6 | 로직 일부 구현 | GF000317 · 318(8-1)에 천공테이프 · 페인트 · 표식라벨 · 천공기날 등 재료 줄이 없음(라벨 · 천공기날은 자재품목에 없음) — 2-1-1 천공기 휘발유 3ℓ/일 · 잡품 95% · 손료(EQ000643)도 안 씀 | 의심 +QF000014 | 소각, 매몰, 훈증, 박피 | 2-1-7 | 로직 일부 구현 | 훈증약제 · 피복제 · 표식라벨 · 벌근훈증약제 5ℓ · 벌근피복제 100개 · 약종별 약제량이 GF000078 · 079(8-3) · GF000083(8-8-1)에 없음(메탐소듐 액제 · 디메틸디설파이드는 자재품목에 없음) | 의심 +QF000023 | 파쇄 소모품 | 2-1-13 | 로직 일부 구현 | 메인파쇄기날 0.00125개/hr · 분쇄기날 0.005개/hr · 잡품 5% 가 GF000323(8-11)에 없음(파쇄기날 자재품목에 없음) | 의심 +QF000003 | 우드그래플 소모성 경비 | 2-1-3 | 로직 일부 구현 | 원문은 「산지에서 우드그랩이 이용되는 품에 적용」 인데 GF000069 만 소모성경비를 더하고 같은 우드그래플 품 GF000070 · 314 · 315 는 안 더함 | 의심 diff --git a/docs/raw/M01_점검/sub1_3/진행.md b/docs/raw/M01_점검/sub1_3/진행.md new file mode 100644 index 00000000..299f54a9 --- /dev/null +++ b/docs/raw/M01_점검/sub1_3/진행.md @@ -0,0 +1,126 @@ +# M01 표 행 대조 진행 · sub1_3 (산림 1장 59표 + 2장 24표 + 3장 16표 = 99표) + +판정 말 — 끝남(흠 0) · 확실(흠) · 의심. 표값 = 원문 표와 자료 표 칸 대조(N/N = 자료 칸 수 · 원문에서 짝을 찾은 칸 수 · 어긋남 0). 찾기 = 엔진 찾기 시험(줄 전부를 찾기로 불러 봄). 로직 = 로직이 그 표를 쓰는 방식(따로 판정). 재확인 = 두 번째 길(줄 단위 수 차례 맞대기)을 다시 돌린 표. + +| 부문·장 | 표 | 끝남 | 확실(흠) | 의심 | 찾기 못함 | 로직 맞음 | 로직 흠·의심 | 미사용 | 해당 없음 | 재확인 | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| 산림 1장 적용기준 | 59 | 59 | 5 | 3 | 7 | 4 | 0 | 33 | 22 | 7 | +| 산림 2장 소요재료 및 기계손료 | 24 | 24 | 5 | 8 | 1 | 5 | 13 | 2 | 4 | 1 | +| 산림 3장 작업장 관리 | 16 | 16 | 1 | 1 | 0 | 13 | 2 | 0 | 1 | 2 | +| 합계 | 99 | 99 | 11 | 12 | 8 | 22 | 15 | 35 | 27 | 10 | + +「확실 · 의심」 열 = 아래 흠 줄글(보고_줄글.md)에 오른 표 수. 표값(칸 값) 흠은 0 — 흠은 모두 표 형식(찾기) · 연결 표시 · 로직 쪽. + +## 산림 1장 적용기준 + +| 표 | 절 | 이름 | 표값 | 표값 근거 | 찾기 | 로직 | 로직 근거 | 흠·의심(줄글) | 재확인 | +|---|---|---|---|---|---|---|---|---|---| +| CF000001 | 1-1-3 | 적용방법 — 품의 일일 작업시간 기준 | 끝남 | 1/1 · 1-01_일반사항.md · 1-1-3 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000002 | 1-2-1 | 수량의 계산 — 소수 처리 · 유효숫자 | 끝남 | 8/8 · 1-02_설계_및_수량.md · 1-2-1 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000003 | 1-2-1 | 총축적 계산 — 측정 기준 | 끝남 | 4/4 · 1-02_설계_및_수량.md · 1-2-1 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000004 | 1-2-1 | 가슴높이지름 2㎝ 괄약 — 대표값별 범위 | 끝남 | 4/4 · 1-02_설계_및_수량.md · 1-2-1 | 값 칸이 범위(참고표) | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000005 | 1-2-1 | 수량 공제 제외 — 포장공종 1개소당 구조물 자리 | 끝남 | 1/1 · 1-02_설계_및_수량.md · 1-2-1 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000006 | 1-2-2 | 설계서의 단위 및 소수의 표준 | 끝남 | 231/231 · 1-02_설계_및_수량.md · 1-2-2 · 원문 73줄 전부 · 종목 병합 칸 채움 · 짝(가·나·다·라)은 병합 아래 줄 표시 | 16줄 겹침(줄이 2개) | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | 의심 · 기타(표 형식) | 예 | +| CF000007 | 1-2-2 | 금액의 단위 표준 | 끝남 | 20/20 · 1-02_설계_및_수량.md · 1-2-2 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000008 | 1-2-3 | 토질 및 암의 분류 — 암 종류별 균열간격 | 끝남 | 12/12 · 1-02_설계_및_수량.md · 1-2-3 | 값 칸이 범위(참고표) | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000009 | 1-2-3 | 돌재료 분류 — 견치돌 · 깬돌 · 깬잡석 형상 비율 | 끝남 | 8/8 · 1-02_설계_및_수량.md · 1-2-3 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000010 | 1-2-3 | 돌재료 분류 — 크기 기준 | 끝남 | 35/35 · 1-02_설계_및_수량.md · 1-2-3 | 값 칸이 범위(참고표) | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000011 | 1-2-3 | 체적의 변화 — 흐트러진상태 L · 다져진상태 C | 끝남 | 4/4 · 1-02_설계_및_수량.md · 1-2-3 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000012 | 1-2-3 | 체적의 변화율 L · C | 끝남 | 51/51 · 1-02_설계_및_수량.md · 1-2-3 · 원문 17줄 전부 · 물결표 「∼」 「~」 섞임은 같은 범위로 읽음 · 폐콘크리트 C 「별도 설계」 글 유지 | 값 칸이 범위(참고표) | 미사용 | 쓰는 로직 없음 | | 예 | +| CF000013 | 1-2-3 | 체적환산계수(f) — 구하는 Q ÷ 기준이 되는 q | 끝남 | 8/8 · 1-02_설계_및_수량.md · 1-2-3 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000014 | 1-2-3 | 토취장 및 골재원 — 규격품 채취 시 사용 못 하는 파 | 끝남 | 1/1 · 1-02_설계_및_수량.md · 1-2-3 | 값 칸이 범위(참고표) | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000015 | 1-2-3 | 사토장 관리 — 토공운반 Q값 반영 시 보통인부 노임 | 끝남 | 1/1 · 1-02_설계_및_수량.md · 1-2-3 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000016 | 1-2-4 | 사용 고재 등 발생재 공제율 | 끝남 | 6/6 · 1-02_설계_및_수량.md · 1-2-4 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000017 | 1-2-6 | 공구손료 및 잡재료 — 명시 없는 잡재료 별도 계상 한 | 끝남 | 1/1 · 1-02_설계_및_수량.md · 1-2-6 | 값 칸이 범위(참고표) | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000018 | 1-2-7 | 운반 — 품에 포함된 소운반 거리 | 끝남 | 1/1 · 1-02_설계_및_수량.md · 1-2-7 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000019 | 1-2-7 | 운반 — 경사면 소운반 거리(직고 대 수평거리) | 끝남 | 2/2 · 1-02_설계_및_수량.md · 1-2-7 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000020 | 1-2-7 | 화물자동차의 적재량(일반 기준) | 끝남 | 286/286 · 1-02_설계_및_수량.md · 1-2-7 · 원문 50줄 전부 · 괄호 (350) (460) (300)(400)(550) 원문 표기 유지 · 시멘트 20톤 637 옆 「25.5톤 화물차는 풀카고기준」 은 원문 칸에 보관 | 12줄 겹침(줄이 2개) | 미사용 | 쓰는 로직 없음 | 의심 · 기타(표 형식) | 예 | +| CF000021 | 1-3-1 | 재료 할증 — 콘크리트 및 포장용 재료 | 끝남 | 17/17 · 1-03_재료_및_노임의_할증.md · 1-3-1 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000022 | 1-3-1 | 재료 할증 — 노상 및 노반재료(선택층, 보조기층, 기 | 끝남 | 8/8 · 1-03_재료_및_노임의_할증.md · 1-3-1 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000023 | 1-3-1 | 재료 할증 — 관류 및 구조물 기초 부설재료 | 끝남 | 2/2 · 1-03_재료_및_노임의_할증.md · 1-3-1 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000024 | 1-3-1 | 재료 할증 — 강재류 | 끝남 | 22/22 · 1-03_재료_및_노임의_할증.md · 1-3-1 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000025 | 1-3-1 | 재료 할증 — 기타재료 | 끝남 | 44/44 · 1-03_재료_및_노임의_할증.md · 1-3-1 · 원문 17줄 · 목재 · 합판 · 레디믹스트 · 현장콘크리트 병합 칸 채움 | 됨 | 미사용 | 쓰는 로직 없음 | | 예 | +| CF000026 | 1-3-2 | 노임 할증 — 작업반장 배치(산지사방, 야계사방, 해안 | 끝남 | 2/2 · 1-03_재료_및_노임의_할증.md · 1-3-2 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000027 | 1-4-1 | 품의 할증 — 작업시기 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-1 | 못 찾음 — 모르는 범위규칙 | 미사용 | 쓰는 로직 없음 — 범위규칙 때문에 못 씀 | 확실 · 기타(표 형식) | | +| CF000028 | 1-4-2 | 품의 할증 — 조림 후 경과연수 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-2 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000029 | 1-4-3 | 품의 할증 — 집단화정도 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-3 | 못 찾음 — 모르는 범위규칙 | 미사용 | 쓰는 로직 없음 | 확실 · 기타(표 형식) | | +| CF000030 | 1-4-3 | 집단화정도 — 사업지 간격 동일 개소 판정 | 끝남 | 1/1 · 1-04_품의_할인․할증.md · 1-4-3 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000031 | 1-4-4 | 품의 할인·할증 — 작업구역 | 끝남 | 21/21 · 1-04_품의_할인․할증.md · 1-4-4 · 원문 7줄 · 작업구역 4줄 + 벌채구역 크기 3줄 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000032 | 1-4-4 | 작업구역 — 개소 간 이격거리 동일 작업구역 판정 | 끝남 | 1/1 · 1-04_품의_할인․할증.md · 1-4-4 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000033 | 1-4-5 | 품의 할인·할증 — 경사도 | 끝남 | 21/21 · 1-04_품의_할인․할증.md · 1-4-5 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000034 | 1-4-6 | 품의 할증 — 작업장까지의 이동거리 | 끝남 | 18/18 · 1-04_품의_할인․할증.md · 1-4-6 | 못 찾음 — 모르는 범위규칙 | 미사용 | 쓰는 로직 없음 | 확실 · 기타(표 형식) | 예 | +| CF000035 | 1-4-7 | 품의 할증 — 하층식생 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-7 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000036 | 1-4-8 | 품의 할증 — 장애물의 정도 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-8 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000037 | 1-4-9 | 품의 할증 — 제거대상 식생 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-9 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000038 | 1-4-10 | 품의 할증 — 토양상태 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-10 | 됨 | 맞음 | GF000017 · 018 · 020 토양조건 할증 3줄 = 표 그대로 | | | +| CF000039 | 1-4-11 | 품의 할인·할증 — 덩굴피복도 | 끝남 | 10/10 · 1-04_품의_할인․할증.md · 1-4-11 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000040 | 1-4-12 | 품의 할인·할증 — 제거대상 피복도 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-12 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000041 | 1-4-12 | 제거대상 피복도 — 제거대상목 기준(조림목 수고 대비) | 끝남 | 1/1 · 1-04_품의_할인․할증.md · 1-4-12 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000042 | 1-4-13 | 품의 할증 — 주행 장애물 상태 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-13 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000043 | 1-4-14 | 품의 할증 — 집재방향 | 끝남 | 24/24 · 1-04_품의_할인․할증.md · 1-4-14 · 원문 7줄 · 병합 칸(집재방향 · 경사) 채움 | 됨 | 맞음 | GF000009 · 061 · 063 · 066 · 067 · 068 · 083 · 309 · 312 · 313 집재방향 할증(구분 · 방향 · 경사 조건 맞음) | 확실 · 기타(연결 표시) | | +| CF000044 | 1-4-15 | 품의 할증 — 횡단운반거리 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-15 | 못 찾음 — 모르는 범위규칙 | 미사용 | 쓰는 로직 없음 | 확실 · 기타(표 형식) | | +| CF000045 | 1-4-16 | 품의 할증 — 규격재 생산 | 끝남 | 2/2 · 1-04_품의_할인․할증.md · 1-4-16 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000046 | 1-4-17 | 품의 할인·할증 — 방제대상목 분포 | 끝남 | 27/27 · 1-04_품의_할인․할증.md · 1-4-17 · 원문 9줄 · 병해충 4 + 재선충 5 | 됨 | 미사용 | 쓰는 로직 없음 | | 예 | +| CF000047 | 1-4-18 | 품의 할인·할증 — 방제대상목 평균 경급 | 끝남 | 15/15 · 1-04_품의_할인․할증.md · 1-4-18 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000048 | 1-4-19 | 품의 할증 — 매개충나무주사 | 끝남 | 3/3 · 1-04_품의_할인․할증.md · 1-4-19 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000049 | 1-4-20 | 품의 할인·할증 — 방제지 사면 | 끝남 | 12/12 · 1-04_품의_할인․할증.md · 1-4-20 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000050 | 1-4-20 | 방제지 사면 — 산록에서 산정부 방향 복합사면 할증 기 | 끝남 | 1/1 · 1-04_품의_할인․할증.md · 1-4-20 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000051 | 1-4-21 | 품의 할증 — 방제지 접근성 | 끝남 | 9/9 · 1-04_품의_할인․할증.md · 1-4-21 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000052 | 1-4-22 | 품의 할증 — 방제 수종 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-22 | 됨 | 맞음 | GF000076 · 077 · 078 · 079 방제수종 할증 | | | +| CF000053 | 1-4-23 | 품의 할증 — 방제목 운반거리 | 끝남 | 18/18 · 1-04_품의_할인․할증.md · 1-4-23 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000054 | 1-4-24 | 품의 할인 — 방제장비 규격 | 끝남 | 7/7 · 1-04_품의_할인․할증.md · 1-4-24 | 됨 | 맞음 | GF000084 굴착기 0.2㎥ 0 · 0.4㎥ 이상 −20 | | | +| CF000055 | 1-4-25 | 품의 할증 — 작업시간 제한 | 끝남 | 10/10 · 1-04_품의_할인․할증.md · 1-4-25 | 정수 시간만 찾음(2.5시간 못 찾음) | 미사용 | 쓰는 로직 없음 | 의심 · 기타(표 형식) | | +| CF000056 | 1-4-26 | 품의 할증 — 소규모 작업물량 제한 | 끝남 | 6/6 · 1-04_품의_할인․할증.md · 1-4-26 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| CF000057 | 1-6 | 원가 구성 비목별 적용기준 | 끝남 | 70/70 · 1-06_원가_작성기준.md · 1-6 · 원문 서식 21줄 중 값 없는 빈 줄(간접재료비 · 소계 · 총원가) 빼고 16줄 · 병합 적용기준 칸 채움 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | 예 | +| CF000058 | 1-7-1 | 거푸집 사용횟수 | 끝남 | 8/8 · 1-07_기타사항.md · 1-7-1 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| CF000059 | 1-7-4 | 공사용수 | 끝남 | 18/18 · 1-07_기타사항.md · 1-7-4 | 값 칸이 범위(참고표) | 미사용 | 쓰는 로직 없음 | | | + +## 산림 2장 소요재료 및 기계손료 + +| 표 | 절 | 이름 | 표값 | 표값 근거 | 찾기 | 로직 | 로직 근거 | 흠·의심(줄글) | 재확인 | +|---|---|---|---|---|---|---|---|---|---| +| QF000001 | 2-1-1 | 체인톱·예취기·아키아윈치·2드럼 케이블윈치·천공기 연료 | 끝남 | 34/34 · 2-01_소요재료.md · 2-1-1 · 원문 7줄(체인톱 3 + 예취기 · 아키아윈치 · 2드럼 · 천공기) · 적용기준 두 줄 합침(천공기) | 됨 | 맞음 | GF000002 · 051 · 052 · 054 · 056 · 062 · 063 · 064 · 076 연료 · 잡품 · 오일(계산 시험 GF000002: 8.4ℓ · 잡품 = 휘발유의 40%) | | | +| QF000002 | 2-1-2 | 경유 사용 임업기계 연료 | 끝남 | 39/39 · 2-01_소요재료.md · 2-1-2 | 됨 | 맞음 | GF000011 · 012 · 065 · 066 · 067 · 068 · 069 · 070 · 073 · 074 · 075 · 308 · 309 · 312 · 313 · 314 · 315 기계명 조건 맞음 | 확실 · 기타(연결 표시) | 예 | +| QF000003 | 2-1-3 | 우드그래플 소모성 경비(1회 교체당 소요비용) | 끝남 | 4/4 · 2-01_소요재료.md · 2-1-3 | 됨 | 의심 | GF000069 만 소모성경비를 더함 · 같은 우드그래플 품 GF000070 · 314 · 315 는 안 더함 | 의심 · 로직 일부 구현 | | +| QF000004 | 2-1-3 | 우드그래플 소모율(Q) 산출 기준 | 끝남 | 17/17 · 2-01_소요재료.md · 2-1-3 | 됨 | 맞음 | GF000069 Q(소모율) 0.02105 | | | +| QF000005 | 2-1-4 | 페인트 및 마킹테이프 재료비 | 끝남 | 31/31 · 2-01_소요재료.md · 2-1-4 | 됨 | 의심 | GF000001 · GF000014 에 페인트 줄 없음 · 3-2 로직 없음 | 의심 · 로직 일부 구현 | | +| QF000006 | 2-1-4 | 노선·경계 표시 간격 및 재료 소모 기준 | 끝남 | 22/22 · 2-01_소요재료.md · 2-1-4 | 됨 | 의심 | QF000005 와 한 묶음(간격 · 본수 기준) | | | +| QF000007 | 2-1-5-1 | 덩굴 약제처리(ha당) | 끝남 | 4/4 · 2-01_소요재료.md · 2-1-5-1 | 됨 | 맞음 | GF000055 농약 10병 · 잡품 10% | | | +| QF000008 | 2-1-5-1 | 덩굴 피복도별 농약 소요량 증감률 | 끝남 | 4/4 · 2-01_소요재료.md · 2-1-5-1 | 됨 | 의심 | GF000055 가 두 줄(밀 · 과소)만 씀 — 과밀 +50 · 소 −20 못 씀 | 의심 · 로직 일부 구현 | | +| QF000009 | 2-1-5-2 | 소금처리(본당) | 끝남 | 8/8 · 2-01_소요재료.md · 2-1-5-2 | 못 찾음 — 모르는 범위규칙 | 흠(확실) | GF000304 이 못 씀(범위규칙) — 소금 재료도 없음 | 확실 · 기타(표 형식) | | +| QF000010 | 2-1-5-2 | 소금처리 잡품율 | 끝남 | 1/1 · 2-01_소요재료.md · 2-1-5-2 | 됨 | 의심 | GF000304 소금 · 잡품 10% 줄 없음 | 의심 · 로직 일부 구현 | | +| QF000011 | 2-1-5-3 | 뿌리제거(100본당) | 끝남 | 10/10 · 2-01_소요재료.md · 2-1-5-3 | 됨 | 의심 | GF000305 농약 · 비닐랩 줄 없음 | 의심 · 로직 일부 구현 | | +| QF000012 | 2-1-6 | 나무주사(100본당) | 끝남 | 23/23 · 2-01_소요재료.md · 2-1-6 | 됨 | 의심 | GF000317 · 318 재료 줄 없음 · 천공기 휘발유 · 손료 안 씀 | 의심 · 로직 일부 구현 | | +| QF000013 | 2-1-6 | 나무주사 ha당 천공수 기준 | 끝남 | 1/1 · 2-01_소요재료.md · 2-1-6 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| QF000014 | 2-1-7 | 소각, 매몰, 훈증, 박피 | 끝남 | 15/15 · 2-01_소요재료.md · 2-1-7 | 됨 | 의심 | GF000078 · 079 · 083 훈증 약제 · 피복제 줄 없음 | 의심 · 로직 일부 구현 | | +| QF000015 | 2-1-7 | 훈증 약종별 약제소요량 | 끝남 | 33/33 · 2-01_소요재료.md · 2-1-7 | 됨 | 미사용 | 쓰는 로직 없음 | | | +| QF000016 | 2-1-8 | 유인 헬기(160ha당) | 끝남 | 9/9 · 2-01_소요재료.md · 2-1-8 | 됨 | 맞음 | GF000081 휘발유 10ℓ · 깃발 8개 · 잡품 95% | | | +| QF000017 | 2-1-9 | 드론 무인 헬리콥터(ha당) | 끝남 | 9/9 · 2-01_소요재료.md · 2-1-9 | 됨 | 흠(확실) | GF000320 무인헬기 휘발유 · 잡품 줄 없음 | 확실 · 로직 일부 구현 | | +| QF000018 | 2-1-9 | 드론 무인 헬리콥터 방제 가능면적·시간 기준 | 끝남 | 2/2 · 2-01_소요재료.md · 2-1-9 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| QF000019 | 2-1-10 | 드론 무인 멀티콥터(ha당) | 끝남 | 8/8 · 2-01_소요재료.md · 2-1-10 | 됨 | 흠(확실) | GF000320 멀티콥터 휘발유 · 잡품 줄 없음 | 확실 · 로직 일부 구현 | | +| QF000020 | 2-1-10 | 드론 무인 멀티콥터 방제 가능면적·시간 기준 | 끝남 | 2/2 · 2-01_소요재료.md · 2-1-10 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| QF000021 | 2-1-11 | 지상 약제살포 | 끝남 | 5/5 · 2-01_소요재료.md · 2-1-11 | 됨 | 흠(확실) | GF000321 경유 · 잡품 줄 없음 | 확실 · 로직 일부 구현 | | +| QF000022 | 2-1-12 | 그물망 피복 | 끝남 | 6/6 · 2-01_소요재료.md · 2-1-12 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | | +| QF000023 | 2-1-13 | 파쇄 소모품 | 끝남 | 7/7 · 2-01_소요재료.md · 2-1-13 | 됨 | 의심 | GF000323 파쇄기날 줄 없음 | 의심 · 로직 일부 구현 | | +| QF000024 | 2-1-13 | 파쇄 잡품율 | 끝남 | 1/1 · 2-01_소요재료.md · 2-1-13 | 됨 | 미사용 | 쓰는 로직 없음 | | | + +## 산림 3장 작업장 관리 + +| 표 | 절 | 이름 | 표값 | 표값 근거 | 찾기 | 로직 | 로직 근거 | 흠·의심(줄글) | 재확인 | +|---|---|---|---|---|---|---|---|---|---| +| QF000025 | 3-1 | 경계표시 | 끝남 | 2/2 · 3-01_경계표시.md · 3-1 | 됨 | 맞음 | GF000001 보통인부 0.2인/ha | | | +| QF000026 | 3-2 | 작업로 예정선 선정 및 표식 | 끝남 | 2/2 · 3-02_작업로_선정.md · 3-2 | 됨 | 흠(확실) | 3-2 로직 없음(누락) | 확실 · 로직 없음(절 누락) | | +| QF000027 | 3-3 | 작업로 설치 | 끝남 | 6/6 · 3-03_작업로_설치.md · 3-3 · 소작업로 · 대작업로 인력구성(병합 칸)을 두 줄에 복사 | 됨 | 맞음 | GF000002 소요 ÷ 벌목부 50% · 보통인부 50% (원문 예시 3.0인씩과 같은 식) | | | +| QF000028 | 3-4-2 | 임산물 운반로 및 작업로 신설비 산정(예시) | 끝남 | 13/13 · 3-04_임산물_운반로_신설_및_보수·복구.md · 3-4-2 | 됨 | 의심 | GF000003 Q 3자리(8.946) ↔ 산림 원문 예시 8.95 | 의심 · 계산식 어긋남 | | +| QF000029 | 3-4-2 | 임산물 운반로 및 작업로 신설비 횡단경사별 할증률 | 끝남 | 4/4 · 3-04_임산물_운반로_신설_및_보수·복구.md · 3-4-2 | 됨 | 맞음 | GF000004 횡단경사 55 미만 0 · 55~65 미만 10 · 65 이상 20 | | | +| QF000030 | 3-4-3 | 임산물 운반로 및 작업로 보수비 - 입지·기상조건별 상 | 끝남 | 6/6 · 3-04_임산물_운반로_신설_및_보수·복구.md · 3-4-3 | 됨 | 맞음 | GF000006 연장 ÷ 기준거리 | | | +| QF000031 | 3-4-3 | 임산물 운반로 및 작업로 보수공정표 | 끝남 | 6/6 · 3-04_임산물_운반로_신설_및_보수·복구.md · 3-4-3 | 됨 | 맞음 | GF000005 보통인부 1.5 · 4.0인/100㎡ | | | +| QF000032 | 3-5 | 예정지정리 | 끝남 | 36/36 · 3-05_예정지정리.md · 3-5 · 원문 9줄 · 인력구분(병합 칸 「특별인부 50% 보통인부 50%」)을 9줄에 복사 | 됨 | 맞음 | GF000007 소요 × 특별 50% · 보통 50% | | 예 | +| QF000033 | 3-5 | 예정지정리 미이용 산림바이오매스 정리량별 할증률 | 끝남 | 4/4 · 3-05_예정지정리.md · 3-5 | 됨 | 맞음 | GF000007 정리량 50 이상 5 · 100 이상 10 | | | +| QF000034 | 3-5 | 예정지정리 수확벌채지 임상별 할인·할증률 | 끝남 | 4/4 · 3-05_예정지정리.md · 3-5 | 됨 | 맞음 | GF000007 침엽 +10 · 활엽 −10 | | | +| QF000035 | 3-5 | 예정지정리 개벌지 크기별 할증률 | 끝남 | 4/4 · 3-05_예정지정리.md · 3-5 | 됨 | 맞음 | GF000007 개벌지 10 · 5 | | | +| QF000036 | 3-5 | 예정지정리 산불피해지 할증률 | 끝남 | 1/1 · 3-05_예정지정리.md · 3-5 | 됨 | 맞음 | GF000007 산불피해 5% × 피해면적 ÷ 조림면적(원문 예시 1.01 과 같음) | | | +| QF000037 | 3-6 | 산물 임내정리 | 끝남 | 30/30 · 3-06_산물_임내정리.md · 3-6 | 됨 | 맞음 | GF000008 정리산물 ÷ 품(원문 예시 16 ÷ 4.3) | | | +| QF000038 | 3-7 | 재해산물 수집 | 끝남 | 120/120 · 3-07_재해산물_수집.md · 3-7 · 원문 3거리 × 10구간 = 30칸 전부 | 됨 | 맞음 | GF000009 수집량 ÷ 품 × 재선충 상향집재 할증 | | 예 | +| QF000039 | 3-8 | 드론 영상 촬영 | 끝남 | 13/13 · 3-08_드론_영상_촬영.md · 3-8 · 원문 5줄 · 빈 칸(-)은 값 없음 | 됨 | 맞음 | GF000010 「계」 줄 0.015 · 0.010 · 0.015 | | | +| QF000040 | 3-8 | 드론 영상 촬영 공정별 처리기준 | 끝남 | 12/12 · 3-08_드론_영상_촬영.md · 3-8 | 됨 | 해당 없음 | 참고 · 정의 · 표시 규칙 표 — 계산식 재료 아님 | | |