1955 Commits
Author SHA1 Message Date
eomsangdonandClaude Fable 5.1 d39eece45e merge(dev): origin/main_desktop_1 합침 — tools/est-test · stmate-test 는 dev(CODEX · 안티그래비티 워크트리 판) 채택 · 데스크탑 메인 폴더 사본 버림(두 번째)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 15:02:46 +09:00
eomsangdon 53aeccc179 Merge remote-tracking branch 'origin/CODEX' into sub_laptop_8 2026-11-08 15:02:17 +09:00
eomsangdon 389ab245e7 chore(tools): 데스크탑 메인 tools 변경 — est-test 개발분 · line-stack-test 걷힘(804-7) — 전역 깃 싱크(요구 689-3) 2026-11-08 15:01:36 +09:00
eomsangdon 87433105f7 wip(est-test): 개발 중 저장 2 — 전역 깃 싱크(요구 689-3) 2026-11-08 15:01:33 +09:00
eomsangdonandClaude Opus 5.5 eb64bf64ac feat(804-8): M02 단가산출 수식 탭 = M01 과 같은 단추 · 저장 한 번 · 머리 단추 줄바꿈 막음(요구 689)
- 공용 줄 쌓기 화면: 페이지가 저장을 맡는 꼴(`page`)을 M02 단가산출 화면에도 — 탭 위 [고침 버림] · [수식 저장] 걷음 · M02 사본 [단가 갱신] · [재생성] · 「M01 새 판」 배지는 그대로 · 「줄 쌓기에서 못 고치는 칸」 안내 줄 걷음(금액 줄 기호 = 율 줄이 앞 줄 금액을 부르는 속 이름 · 화면에서 고칠 값 아님 · 보존 그대로)
- M02 머리 단추 = [초기화] · [삭제] · [되돌림] · [저장] — 단가산출 상세는 [새로고침] 숨김 · [되돌림] · [저장] = 고침 있을 때만(기본정보 + 수식) · [저장] 한 번 = 수식 먼저 저장(새 판 · 수식 칸 받음) → 단가산출 문서 저장 · [되돌림] · 버리고 떠남 = 수식 캐시도 비움
- 보기 고르기(수식 · 단가산출 상세) = M01 처럼 머리 줄 · [항목 추가] · [율 줄 추가] 걷음(줄 더하기 = 수식 탭 [+] 단가산출 · 일위대가 복사) · 일위대가 줄 모달 안 상세는 그대로
- 머리 단추 줄 맞춤(부품 상자 `fitButtonRow`): 단추가 아래로 떨어지거나 제목이 여러 줄이면 아이콘 단추는 아이콘만(글 = 풍선 도움말) · M01 단추 다섯 · M02 단추 넷 아이콘 · 단가산출 상세 긴 이름은 제목 안에서 줄바꿈
- 사본 할일 막힘 알림 글 = [저장] 또는 [되돌림] 먼저
- 시험: test_804_7 에 M02 단추 · 저장 한 번 · 줄 맞춤 지킴 더함 · test_804_4 권한 시험에 수식 저장 판 = 페이지 저장 판 이어 씀 확인 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 14:56:31 +09:00
eomsangdon ef5fb0632f Merge branch 'main_laptop_1' of C:/Program_coding/임도설계 및 견적자동화 프로그램 개발 into sub_laptop_3 2026-11-08 14:24:47 +09:00
eomsangdon fcf5f457f0 Merge remote-tracking branches 'origin/CODEX' and 'origin/main_laptop_1' into sub_laptop_3 2026-11-08 14:23:15 +09:00
eomsangdon 1fee1e950c Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-11-08 14:23:14 +09:00
eomsangdonandClaude Fable 5.1 e699d95857 knowledge(요구689): M02 보기 = M01 · 저장 단추 · 전역 싱크
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 14:23:10 +09:00
eomsangdon ef3cc96509 Merge remote-tracking branch 'origin/CODEX' into main_laptop_1 2026-11-08 14:21:03 +09:00
eomsangdon af2d9826cf Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1 2026-11-08 13:36:24 +09:00
eomsangdonandClaude Opus 5.5 abe722f378 feat(804-7): 줄 쌓기 「수식」 탭 수정 2차(요구 687 · 688)
- 로직 삭제 = 서버 정본에서 바로(로직 줄 + 수식 문서 · `/logic/remove`) → 로직 목록 · 부르는 곳(다른 수식 문서 · 일위대가 조합 · M02 단가산출 바탕)이 있으면 확인 창 전에 막고 까닭(`/logic/users`)
- 머리 단추 다섯 — [신규 로직] · [로직 복사/생성] · [로직 삭제] · [되돌림] · [저장] · [저장] 한 번 = 기본정보 + 수식 · [되돌림] = 둘 다 저장 뒤 상태 · 줄 쌓기 [고침 버림] · [수식 저장]은 M01 에서 걷음(M02 사본은 그대로)
- 합계 상자 화면 아래 고정 · 원문 손잡이 브라우저 오른쪽 끝
- 왼쪽: 그룹 머리 한 줄(접어도 [+] · X 자리 그대로) · 「비우면 번호 없음」 · 가나다 체크 걷음 · 금액 줄 한 줄 배치 · 이름 단추 = 그 이름 찾은 찾기 창 · 수량 기호 = 값 + 출처 꼬리표 · [+] 메뉴(단가산출 · 율 · 식 · 근거 글 · 틀 찾기 걷음) · 식 글 눌러 그 자리 고침([식 고치기] 걷음) · 단위 = 공용 단위 고르기(친 글도 씀) · 만약 안 탄 갈래 숨김
- 오른쪽 기계 줄: 비목 수 링크 걷음 · 제목 줄 누름 = 시간당 경비 펼침 · 펼친 제목 보통 글
- 요소 키가 기호 이름으로 샌 것(`k_EQ0006`) — 변환기가 자원 이름으로(`k_하베스터`) · 수식 문서 다시 빌드(10 파일 · 78 기호 · 금액 같음)
- 시험 페이지 `tools/line-stack-test` · main.py 마운트 · vite 프록시 · M01 왼쪽 「시험: 줄 쌓기」 걷음
- 단위 표준 표 읽기 = 로그인만(M02 일반 사용자 단위 목록)
- 시험: test_804_7 새로 · 804_2 · 804_4 이음 글 고침(createStackScreen 줄 나뉨) · 552_3 여는 단추 기본 ⋯ 꼴로 고침(부르는 쪽이 [+] 로 바꿀 수 있음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 13:33:36 +09:00
eomsangdon 944f52cd6f Merge commit 'e5e34d82fb9a2361b8bb3f3eebffe20a61ededef' into CODEX 2026-11-08 12:53:06 +09:00
eomsangdonandClaude Fable 5.1 fd188fdf2b knowledge(요구688): 그룹 닫으면 X 밀림
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 12:46:16 +09:00
eomsangdonandClaude Fable 5.1 89292a75df knowledge(요구687): 줄 쌓기 수정 18 · 그림 셋
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 12:45:09 +09:00
eomsangdonandClaude Fable 5.1 e5e34d82fb merge(dev): origin/main_desktop_1 합침 — tools/est-test · stmate-test 는 dev(CODEX · 안티그래비티 워크트리 판) 채택 · 데스크탑 메인의 묵은 사본 17 파일 버림 · 경쟁사 프로그램 폴더 재편은 그대로 받음
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 12:16:18 +09:00
eomsangdon 53a33b07c1 Merge remote-tracking branch 'origin/안티그래비티' into sub_laptop_8 2026-11-08 12:15:03 +09:00
eomsangdon ebcec5ffa0 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_8 2026-11-08 12:15:02 +09:00
eomsangdon 47f4a729b4 chore(tools): line-stack-test 시험 페이지 — 시놀로지가 복사한 랩탑 판(메인 3e64aa2a7 과 같은 내용 · 합칠 때 같은 추가로 풀림) 2026-11-08 12:13:58 +09:00
eomsangdon 640903d6ef wip(stmate-test): 개발 중 저장 — 전역 깃 싱크(요구 686-2) · tools/stmate-test 2026-11-08 12:13:22 +09:00
eomsangdon 1beaefa529 wip(est-test): 개발 중 저장 — 전역 깃 싱크(요구 686-2) · tools/est-test · resources/tester 2026-11-08 12:12:37 +09:00
eomsangdon 2885a51854 knowledge(경쟁사): 경쟁사 프로그램 폴더 재편(STmate 분석 → STmate · 오솔길 분석 → 오솔길/ · 다산소프트 · EST example) + tools est-test · stmate-test 시험 프로그램 — 데스크탑 메인 미커밋 자료(요구 686-2) 2026-11-08 12:08:45 +09:00
eomsangdonandClaude Fable 5.1 db1d976ea8 merge(sub_laptop_2): 802-4b-1 멈춘 wip(5799ccbe9 · 8406fd236 · 32208c357) 보관하고 메인 판 채택 — 충돌 M02_Doc_Sk · Doc_Sk_Path 는 804 흐름(메인)으로 · wip 는 tag wip/802-4b-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 11:47:53 +09:00
eomsangdon cf7f62069e Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_8 2026-11-08 11:46:46 +09:00
eomsangdonandClaude Opus 5.5 7480aebc96 feat(804-6): 옛 수식 탭 걷음 · 줄 쌓기 = 첫 「수식」 탭(요구 686-1)
- M01 단가산출 기초 · M02 단가산출 상세 보기 = 수식(줄 쌓기 · 기본) · 단가산출 상세 둘
- 줄 쌓기에 M02 사본 [단가 갱신] · [재생성](미리 셈 → 알림 → 저장 · 고침 있으면 막음) · 판 바뀜(409) 문서 그대로면 새 판으로 다시 저장 옮김
- 옛 수식 문서 화면 걷음(formula_doc_screen · defs · edit · lines · math · math_model · rows · sub · formula_doc.css) · 이미 안 부르던 796-2 수식 나무 화면(LogicLab_Equation · Defs · Var · css · M01_Logic_Equation_Model · sheet_formula_cell · ui_template_equation_defs · edit) 걷음 — 18 파일 4,860 줄
- formula_doc_render = 줄 쌓기가 쓰는 reach 만 · 옵션 가로 CSS 는 line_stack.css 로
- 남김 = 계산기 common_util_formula_doc* · 서버 길 · 옵션 · 사본 표지 · 합계 상자 · KaTeX · 시험 페이지 tools/line-stack-test
- 시험: test_796_2 지움(걷은 화면 원본 검사뿐) · 799_1 · 800_2 걷은 화면 줄만 뺌 · 799_2 · 802_4b2 · 804_2 보기 · 화면 파일 대상 바꿈 · test_804_6 새로(걷은 파일 · 부르는 곳 없음 · 첫 탭 · 사본 할일)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:09:44 +09:00
eomsangdonandClaude Fable 5.1 04caf618bb knowledge(요구686): 수식 탭 삭제 · 줄 쌓기 기본 · 전역 깃 싱크
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 10:45:22 +09:00
eomsangdon d4fbe755b0 Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1 2026-11-08 08:43:18 +09:00
eomsangdonandClaude Opus 5.5 6f678fb91b feat(804-5): 줄 쌓기 표 값 이름 · 산출근거 고정 · 그룹 글 · 짝 강조 채우기 · 틀로 저장 걷음(요구 683~685)
- 표 값 기호 화면 이름 = 고른 줄 이름(Q_수량2^표 → Q_특별인부 · 겹치면 2 · 3) — 왼쪽 · 산출근거 같음 · 칸 글은 화면 이름 ↔ 기호로 오감(문서 기호 그대로)
- 단위 「줄 따름」 안 보임 · 표 값만 든 묶음 머리 = 「표 값」 · [식으로 풀기] → 「식 고치기」
- 산출근거 = 넓을 때 화면 고정 + 자기 스크롤(높이 = 스크롤 영역) · 나란히 놓는 탭 폭 1240 → 1040
- 글 「항목」 → 「그룹」(그룹 이름 · + 그룹 · 그룹 지움)
- 짝 강조 = 왼쪽 카드 · 그룹 머리 · 오른쪽 줄 모두 같은 바탕 채우기(테두리 걷음)
- [틀로 저장] · 내 틀 목록(브라우저 저장) 걷음 · 공식 틀 고르기 = M01 단가산출 결과만
- 시험 test_804_4 표 값 화면 이름 1,518 전수(겹침 없음 · 식 글 왕복 같음) 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 08:42:41 +09:00
eomsangdonandClaude Fable 5.1 c18c3036eb knowledge(요구685): 내 틀 저장 안 함
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 08:34:16 +09:00
eomsangdonandClaude Fable 5.1 25bbc084ec knowledge(요구684): 짝 강조 색상 채우기 통일
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 08:29:40 +09:00
eomsangdonandClaude Opus 5.5 af576e5a7a feat(804-5): 줄 쌓기 왼쪽 손질 — 자원 이름 단추 · 항목 띠 · 끌어 옮기기 · X 지움 되돌리기 · 율 번지 읽는 이름
- 인력 · 기계 · 재료 · 단가산출 줄 자원 이름 = 단추(이름 · 규격) → 찾아 넣기 창 → 바꿈 · [찾아 바꾸기] 걷음
- 항목 머리 = 띠(바탕 · 왼쪽 굵은 띠 · 번호) · 줄 = 들여쓰기 + 왼쪽 세로 선 · 가나다 한 단 더 · 접기
- 순서 = 손잡이 끌기(줄 = 항목 안 · 사이 · 항목 끌기 · 자리 선 표시) · [↑][↓] 걷음
- 지움 = X 아이콘 · 확인 없이 지우고 알림 눌러 되돌리기(그 뒤 고침 있으면 못 함)
- 산출근거 율 줄 「번지.복사N.노무비」 → 그 조각 이름 · 값 식 = 번지 값 · 합계 덧셈 항 넷 넘으면 항목 소계끼리
- 알림 부품: 누름 동작 알림은 같은 문구 버림 안 함(두 번째 지움 되돌리기 안 뜨던 흠)
- 글 · 근거 카드 높이 늘던 흠 고침 · 시험 test_804_4 끌어옮김 · 번지 이름 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 08:29:31 +09:00
eomsangdonandClaude Fable 5.1 6a9a98d6d3 knowledge(요구683): 줄 쌓기 이름 · 스크롤 · 그룹 · 그림
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 08:29:10 +09:00
eomsangdonandClaude Fable 5.1 ca11090104 knowledge(요구682): 줄 쌓기 왼쪽 손질
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 08:07:47 +09:00
eomsangdon d7bc5cac83 Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1 2026-11-08 08:04:56 +09:00
eomsangdonandClaude Opus 5.5 9c43296145 feat(804-4): 줄 쌓기 다른 단가산출 · 일위대가 복사해 펼침 — M01 은 M01 전체만
- M02 단가산출 줄 쌓기 [+ 줄] 「다른 단가산출 · 일위대가 복사」 = 찾기 창 탭 [M01 전체 | M02 단가산출 | 일위대가](위 층 · 자기 SK 막음)
- 고름 = 그 문서 전체를 복사해 이 단가산출 항목 · 줄로 펼침(참조 줄 없음 · 원본 연결 없음 · 머리 글 「○○에서 복사」 · 옵션 = 고른 값으로 굳힘 · 기호 겹침 = 새 이름)
- 일위대가 = 로직 줄 → M01 문서 × 수량 ÷ 배 · 참조 SK → 그 SK 문서 × 수량 ÷ 배 · 고정 노임 · 자재 · 기계 · 손 단가 · 율(비목 기준) · 못 옮김 = 「⚠」 글 줄
- 율 줄 밑 = 그 조각 줄만(금액번지 복사N) — 앞 줄 · 다른 조각 안 섞임
- 서버 조각 길 GET …/sangeun/{키}/doc/take?src= (M02_Doc_Take) · 화면 넣기 common_util_line_stack_copy.ts
- 빈 SK(바탕 · 줄 없음)에 수식 길로 저장한 문서가 빈 씨로 덮이던 것 고침(M02_Doc_Sk.settle 차례)
- [단가 갱신] = 사본 값 줄 자원 단가만 지금 M01(구조 · 품 · 식 그대로)
- 시험 test_804_4_line_stack_take 6 · helper_804_2 「복사」 할일

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 08:04:16 +09:00
eomsangdonandClaude Fable 5.1 feae4e60d7 knowledge(요구681): 참조 아니라 복사 · 전체 보임 · 기계경비 설명
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 07:35:48 +09:00
eomsangdonandClaude Fable 5.1 f9cafaa89b knowledge(요구680): M01 안 부름 · M02 부름 · 일위대가 여러 항목
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 07:28:49 +09:00
eomsangdon 71e7165250 Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1 2026-11-08 01:27:57 +09:00
eomsangdonandClaude Opus 5.5 0eb7822772 feat(804-3): 줄 쌓기 수정 1차 — 표 값 고르기 · 산출근거 들여쓰기 · 찾아 넣기 창 · 짝 강조 · 기계 이름
- 표 값(요구 678-1): 「표」 꼬리표가 글뿐이라 눌러도 안 열림 → [표 · 고른 줄 · 칸 ▾] 단추 = 품셈 표 창(값 칸 누름 = 그 줄 · 칸)
  옵션에 매인 조건(폼타이규격 등) = 그 줄만 고름 · 다른 줄 막힘 + 「위 옵션에서 고름」 안내 · M02 길 표 값 글 수도 고름
- 산출근거 들여쓰기(678-2): 머리 0 · 항목 1 · 가나다 2 · 줄 = 항목 + 1 · 펼친 블록 = 그 줄 + 1(수준 = stackLevels)
- 찾아 넣기(678-3): [+ 줄] 인력 · 재료 · 기계 · 다른 단가산출 = M02 [항목 추가] 창 그대로(그 종류만) · 고르면 줄 생김(닫으면 없음)
  있는 줄 = [찾아 바꾸기](같은 로직 기계 = 지역 · 보정작업 넘김 그대로) · 옛 찾기 칸 + 고름 목록 · 기종 코드 고르개 걷음
  빈 틀 = [찾아 넣기](돈 아닌 단가산출 결과만 — Find_Pick results)
- 짝 강조(679-1): 오른쪽 줄 누름 = 왼쪽 카드 강조 + 보이는 자리로 · 왼쪽 누름 = 오른쪽 같은 줄 강조
- 기계 이름(679-2): 기계 줄 = 기종 이름 · 규격 · machine-hourly 답에 로직 키 · 원문번호 더함(기존 칸 그대로)
- 모델 common_util_line_stack_pick.ts(boundConds · currentRow · pickCell · addPicked · repick · stackLevels)
- 시험 test_804_2 넓힘 3(표 고름 = 값 + 출처 · 서버 셈 같음 · 찾아 넣기 넷 = 요소 키 · 서버 금액 · 다시 고름 · 수준)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:27:20 +09:00
eomsangdonandClaude Fable 5.1 a0f0c15caf knowledge(요구679): 줄 쌓기 추가 수정 — 하이라이트 · 기계 이름
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 01:09:13 +09:00
eomsangdonandClaude Fable 5.1 6c14e73a5c knowledge(요구678): 줄 쌓기 탭 수정 1차
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-08 00:57:07 +09:00
eomsangdonandClaude Opus 5.5 c11baa441c feat(804-2): 단가산출 「줄 쌓기」 새 탭 — M01 단가산출 기초 · M02 단가산출 · 기계 시간당 경비 수식 펼침 · KaTeX
- 공용 화면 A00_Common/line_stack_* — 위 옵션 · 왼쪽 줄 쌓기 편집 · 오른쪽 산출근거(경쟁사 시트 꼴) · 맨 아래 합계 상자
- 모델 common_util_line_stack.ts — 수식 문서 ↔ 줄 쌓기 한 벌 왕복(차림 = 문서 칸 `쌓기` · 기본과 다를 때만)
- 기계 · 단가산출 줄 단가 수 누름 = 그 문서를 같은 계산기로 셈해 펼침(손료 식 · 운전경비 · 노 · 재 · 경 · 계)
- 공식 묶음 · 틀 풀기(단가산출 결과 → 이 문서 식) · 내 틀(이 브라우저) · 「내 틀 (… 에서 변경)」
- 수식 탭과 같은 길 · 같은 캐시 · 탭 바꾸면 다시 읽음 · [수식 저장] = 서버 정본 셈
- KaTeX(MIT) — vite 미리 묶기에서 뺌(rolldown 이 글자 규칙 서로게이트 범위를 깨뜨림)
- 시험 test_804_2 6(왕복 1,518 · 금액 · 기계 펼침 = hourly · 틀 풀기 · 저장 · 이음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:34:29 +09:00
eomsangdonandClaude Fable 5.1 26bd46041e knowledge(요구677): 메인 새 탭 · 기계 수식 · 라이브러리
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-07 23:40:22 +09:00
eomsangdonandClaude Opus 5.5 3e64aa2a79 feat(804-1): 시험 페이지 「줄 쌓기 단가산출」 tools/line-stack-test + M01 왼쪽 패널 단추
- 왼쪽 줄 쌓기(항목 · 줄 일곱 종류 · 공식 틀 셋 · 식으로 풀기 · 틀로 저장 · 표에서 고르기 · 끌어 순서) · 오른쪽 경쟁사 꼴 산출근거(분수 기호식 = 값식 = 결과 · 소계 · 합계) · 아래 비목 상자 · 엑셀 글 붙여넣기 · 저장 = localStorage
- 견본 셋 금액 = 거창 파일 원 단위(5,323 · 52,796 · 94,215 비목 넷) · 확인 = node tools/line-stack-test/check.mjs
- 접근 = main.py 정적 마운트 /tools/line-stack-test · vite 프록시 한 줄(개발 포트에서 열림) · M01 왼쪽 패널 맨 아래 「시험: 줄 쌓기」 새 탭
- 라이브러리 0(분수 = CSS) · 지우기 = 폴더 + 표시 주석 셋 덩이

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:19:38 +09:00
eomsangdonandClaude Fable 5.1 d59874d4b4 knowledge(요구676): 시험 페이지 · M01 왼쪽 패널 접근
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-07 23:02:57 +09:00
eomsangdonandClaude Fable 5.1 a77d257b1c knowledge(요구675): 줄 쌓기 좋음 · 새 공식 · 공식 변경
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-07 23:00:56 +09:00
eomsangdonandClaude Fable 5.1 1a7e6d5eb7 knowledge(요구674): 조각 안 어려움 · 경쟁사 출력 파일
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-07 22:54:36 +09:00
eomsangdonandClaude Fable 5.1 889931cc55 knowledge(요구673): M01 기초 구조 변경 가능
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-07 22:42:01 +09:00
eomsangdonandClaude Fable 5.1 973872fe43 knowledge(요구672): tool 폴더 · 품셈 구조 분석 · 지정 방식 + 자유도
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-07 22:40:47 +09:00
eomsangdonandClaude Fable 5.1 ff9d2941c1 knowledge(요구671): 시험 폴더 새 페이지 · 품셈 조합 변수 · 외부 라이브러리 조사
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-07 22:13:13 +09:00
eomsangdonandClaude Fable 5.1 3a7ddb8583 knowledge(요구670): 엑셀 · 기호식 = 값식 출력 · 옵션 유지 · tool 참고 · 폭넓게
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-07 21:57:26 +09:00
eomsangdonandClaude Opus 5.5 1bb9ba389e knowledge(요구669): 작업 중지 · 대화 · 경쟁사 대비 사용자 입장
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:45:19 +09:00
eomsangdonandClaude Opus 5.5 824168c1ca fix(803-2b): 기호 정의 = 세 번째 칸 그대로(좁은 화면에서 아래로 떨어짐 걷음)
- 까닭 — 수식 탭 너비 900px 아래(1366 · 원문 펼침)에서 세 영역을 한 줄로 쌓는 규칙이 기호 정의를 기호 식 · 값 식 아래로 내림(요구 667-3 수평 구조 어긋남)
- 그 규칙 걷음 — 좁으면 칸 너비만 줄고 칸 안에서 줄바꿈

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:30:48 +09:00
eomsangdonandClaude Opus 5.5 970fd441ba fix(803-2b): 저장 전 새 로직 기호 식 영역 누름 = 입력 · 편집 줄 너비 전부
- 까닭 — 수식 화면 고침 조건이 「관리자 + 로직 키」 · 신규 로직은 저장 전 키가 없어 영역 누름 처리가 아예 안 붙음(GC000211 같은 저장된 로직은 누름 됨)
- M01 저장 전 새 로직도 고침 · 고친 문서는 캐시 「""」 열쇠 · 미리 셈은 임시 키 · [수식 저장] · [고침 버림]은 키가 생긴 뒤
- 페이지 [저장]으로 새 키가 생기면 고친 문서를 그 키(빈 문서)로 이어 감(이름 · 단위 = 새 로직) · 신규 로직을 다시 열면 옛 고침 걷음
- M02 저장 전 새 SK = 자리 표시(저장하면 M01 사본) — 읽기만 그대로
- 편집 중 줄 = 그 줄 너비 전부(빈 줄 입력 칸이 2em 으로 줄던 것 · 「a =」 「3」 꺾임 고침)
- 서버 · 계산기 · 문서 꼴 그대로 · 새 라이브러리 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:27:54 +09:00
eomsangdonandClaude Opus 5.5 dcae2eb705 knowledge(요구668): 기호 식 눌러도 입력 안 나옴
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:11:12 +09:00
eomsangdonandClaude Opus 5.5 9f969f0c3f fix(803-2a): 수식 편집기 쉼표 글 꼴(「a, b」)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:09:03 +09:00
eomsangdonandClaude Opus 5.5 f5545dd62b feat(803-2a): 수식 탭 세 영역 · 하나의 수식 영역 · 합계 상자 · 설명 글 지움
- 세 영역 가로 — 기호 식(줄바꿈으로 이어진 하나의 수식 영역) | 값 식(같은 높이 줄) | 기호 정의
- 맨 아래 합계 상자 = 단가산출 상세 탭 부품(노무비 · 재료비 · 경비 · 계) · 옵션 아래 계 줄 · 설명 글 · 표지 안내 줄 걷음(오류 = 그 기호 빨강)
- 기호 식 줄 누름 = 그 자리 편집기(치는 대로 조판 — / 분수 · _ 아래첨자 · ^ 위첨자 · 괄호 · 화살표 구조 이동 · Enter 새 줄 · Esc 나옴 · 한글 조합) · 편집 중만 영역 위 수식 도구 줄
- 금액 줄 = 영역 안 식 줄 「노무비 형틀목공 = w × Q」(머리 첫 말 = 비목) · 「+ 식 줄」 「+ 금액 줄」 「+ 묶음」 · 비목 고르기 칸 걷음 · 묶음 = 도구 줄 [묶음]
- 저장 전 새 로직 = 세 영역 빈 화면 · 끊은 단가산출 블록도 같은 편집기
- 새 라이브러리 없음 · 서버 길 · 계산기 · 문서 꼴 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:07:59 +09:00
eomsangdon 2057e0ec02 Merge branch 'main_laptop_1' of C:/Program_coding/임도설계 및 견적자동화 프로그램 개발 into sub_laptop_3 2026-11-07 20:48:19 +09:00
eomsangdonandClaude Opus 5.5 0b94ece486 knowledge(요구667): 수식 탭 사용법 1차 요구 원문
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:47:41 +09:00
eomsangdonandClaude Opus 5.5 32208c3577 wip(802-4b-1): 중지 — 빈 산근 = 엑셀 0 원 줄(전후 다름 6 블록 고침 · 시험 한 건)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:30:28 +09:00
eomsangdonandClaude Opus 5.5 cbd02c09e9 wip(802-4b-3): 중지 — 펼친 단가산출 블록 격자 전체 폭(정의 영역 전체 폭 · 기호 줄 오른쪽 칸 자리) · 금액 줄 비목 합 참조 값(moneyScope = 앞 줄 누계) · 빨강 헬퍼 틀(시험 · ORCA 안 돌림)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:30:13 +09:00
eomsangdonandClaude Opus 5.5 6a47356e67 knowledge(요구666): 전체 중지 · 프론트엔드 사용법 먼저 요구 원문
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:30:03 +09:00
eomsangdonandClaude Opus 5.5 8406fd2367 fix(802-4b-1): M02 파생 SK 옵션 값 = M01 문서 기본(자재지역 자동값 0 → 포항) — 받은 값이 이김 · 금액 그대로
- `M02_Doc_Copy.given_of` = M01 수식 탭 기본(`master_equation.given_of` · 자동값 › 기본 제안) › SK 받은 값(빈 값 = 기본) — 옛 = 엔진 자동값만(입력 기본 없는 자재지역 = 0)
- SK 파생 · 일위대가 · SC 줄 문서 · 가져온 식 문서 모두 같은 길 · 8004 GC000211 바탕 SK 자재지역 포항 · 계 124,419 그대로
- 시험 test_802_4a 옵션 값 한 건(빈 입력 · 빈 글 = M01 기본 · 울진 = 울진 · 금액 = 옛 셈) · 795_1 · 795_2 · 795_3 · 795_4 · 802_4a · 802_4b1 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:29:18 +09:00
eomsangdonandClaude Opus 5.5 c80b5ae9d0 feat(802-4b-2): M02 단가산출 수식 탭 = 공용 수식 문서 화면 · 꺼진 줄 = 계산 변수 거침
- M02 SK 「수식」 탭 = M01 과 같은 공용 화면(`createDocScreen` · 길 앞머리만 `/m02/layers/{층}/sangeun/{키}/doc`) — 옛 M02 수식 나무 화면(`_Sangeun_Equation.ts` · 796-2b · 2c · 2e · 800 · 801 꼴) 걷음
  · 옵션 = SK 문서 값(저장) · [단가 갱신] · [재생성](미리 셈 → 알림 계 전 → 후 · 버리는 것 → 저장) · 「M01 새 판 있음」 배지(`formula_doc_copy.ts`)
  · 단가산출 연결 펼침 머리 「연결 / 끊음」 · [연결 끊기] · [다시 잇기] · 끊은 문서 안 줄 그 자리 고침(`formula_doc_sub.ts`) · 저장 몸 {문서, 끊음, 판}
  · 층 쓰기 권한 없음 = 읽기만(옵션 잠금 · 단추 없음 · 「읽기만」) · 판 바뀜 409 = 사본 같으면 새 판으로 한 번 더 · 저장 뒤 페이지 판 · 상세 탭 `수식` 칸 새로
  · 저장 안 한 SK(일위대가 줄 모달) = 「저장한 단가산출만」 글
- 서버 — 수식 길 답에 `연결`(화면 캐시 기록 · `M02_Doc_Screen.record`) · 연결 후보 · 표 길(층 읽기 · M01 후보 · 표 그대로 · main 등록)
- 꺼진 줄(요구 663-1 마저) — 곱 인수 하나가 선택으로 꺼진 0(만약 0 갈래 · 식 줄 기호면 그 식으로 거침)이면 숨김 · 입력 · 글 0 은 그대로
  · 판정을 순수 파일로(`common_util_formula_doc_view.ts` · Node 로 시험) · 1,518 문서 기본 옵션 꺼짐 158 → 203 줄(꺼진 줄 금액 모두 0) · GC000211 폼타이 아님 = 세 규격 줄 모두
- 화면 파일 700 줄 — 고침 함수 · 옵션 가로 · 연결 펼침을 새 파일로(`formula_doc_edit` · `_options` · `_sub`)
- 시험 test_802_4b2(5 · 헬퍼 helper_802_4b2_off.cjs) · 옛 시험 둘 고침 — test_793_3b · test_796_2b 가 걷은 옛 M02 수식 화면 글을 읽음 → 없음 확인으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:20:52 +09:00
eomsangdonandClaude Opus 5.5 5799ccbe90 feat(802-4b-1): 엑셀 불러오기 가져온 식 SK = 수식 문서 — 숫자 값 · 이음 = 연결 + 엑셀 값 기억 · 산근 속 산근 = 형제 단가산출 연결 · 고른 소요량 = 표 연결 · 표지
- 새 `M02_Doc_Import` — 엑셀 단가 기준 엔진 줄(가져온 줄 + SK 고침)을 `master_doc_convert` 로 문서로 옮김(손 값 = 숫자 · 중간 = 식 줄 · EST 얼린 변수 = 숫자 · 덧줄 · 단계 · 끝전 · 자리 · 합밖 그대로)
  · 인력 · 재료 · 유가 · 환율 이음 = 요소 · 기계 EQ = 기계경비 단가산출(칸 = 그 줄 비목 · 시간당 자료 없음 = 숫자 + 설명) · 로직 이음 = 단가산출 · 참조 = 형제 SK 단가산출(칸 = 참조비목)
  · 연결 칸 `엑셀` = 엑셀 값 기억 · 머리 `가져온` {파일, 시트, 행, 갈래, 코드, 표지(797-1a · 못 읽음 까닭 그대로)}
  · 소요량 · 계수 고른 칸(797-2) = 그 수 자리에 표 연결 기호(꼴 pct · kgt = ÷ 배)
- 셈 = 수식 문서 계산기 — [엑셀 단가 그대로] = 엑셀 값 기억 연결을 숫자로 · [우리 단가로] = 연결 값 · 형제 SK = 부르는 쪽 단가기준(안쪽이 바뀌면 따라감)
- SK 금액 = 문서 셈(`M02_Doc_Sk.on_run` · 가져온 식도 수식 사본 · 파생 = 셈마다 지음 · 따로 = 수식 길 저장본 · 가져온 줄 · 이음 · 고름 고침 409)
- 수식 길(…/sangeun/{키}/doc · preview · save) 가져온 식도 · 사본 표지 단가기준 · 단가갱신 = 문서 그대로
- TS 짝 `common_util_formula_doc_import.ts` `importView`(브라우저 계산기 엑셀 보기 · 거울 시험)
- 옮기기 `M02_Doc_Migrate` 가 가져온 식 SK 도 대조 · 씀(--apply --backup) — 실제 storage 보기만: 개인 가져온 식 1 같음
- 시험 test_802_4b1(9) · 불러오기 시험 793_1 · 793_2 · 793_3a · 793_3b · 797_1a · 797_1 · 797_2 · 802_4a 147 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:03:26 +09:00
eomsangdon c07cd1d133 Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1 2026-11-07 19:44:02 +09:00
eomsangdonandClaude Opus 5.5 a003587aca fix(802-3): 선택으로 꺼진 금액 줄 숨김 · 값 식 한 번 · 옮김 군더더기 걷음(브레인 사진 지적)
- 663-1: 수량 · 단가가 만약으로 0 을 탄 금액 줄 = 안 보임(GC000211 폼타이 9.5 · 7.9 ㎜) · 규격 바꾸면 그 줄만 · [선택으로 꺼진 줄 보기] = 고침 모드에서만 · 정의 영역도 꺼진 줄 기호 뺌
  1,518 문서 금액 줄 5,912 중 기본 옵션에서 꺼짐 145 줄(107 문서) · 옵션에 따라 꺼질 수 있는 줄 305 줄(141 문서)
- 식 안 만약이 0 · 1 을 타면 그 항(+ 0 · × 1)도 안 보임 — 고침 글은 그대로
- 수 하나 · 기호 하나 줄 값 식 = 한 번만(「= 1 = 1」 없음) · 수량 없음 「× 1」 자리는 줄에 마우스 올릴 때만
- 옮김 slim(master_doc_convert) — + 0 · × 1 · ÷ 1 지움 · A × k ÷ 100 → A × k% · 빌더 다시(목록 xlsx 는 안 씀) · 대조 그대로(기본 1,519 같음 · 옵션 다름 1 · 저장 1,518) · 31 파일 글만
- 시험 test_802_3 + 군더더기 없음 1(12) · 802_1 · 802_2 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:43:00 +09:00
eomsangdonandClaude Opus 5.5 1f7525e502 chore(802-4a): M02 마스터 층 일위대가 · 조합 수식 문서 사본 옮김 — M02_Doc_Migrate --apply --backup tmp/802_4a · 같음(빈 SK 2 = 「—」 · 가져온 식 1 = 802-4b) · 요구 659-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:42:47 +09:00
eomsangdon d98e2204e8 Merge branch 'sub_laptop_2' of C:/Aislo_wt/sub_laptop_2 into main_laptop_1 2026-11-07 19:40:40 +09:00
eomsangdonandClaude Opus 5.5 040fc6458e feat(802-4a): M02 단가산출 · 일위대가 · SC · B09 사본 = 수식 문서 사본 — 문서 셈 · 수식 길 · 옮기기 도구 · 대조
- 문서 칸 `수식`(옛 `사본` 은 802-5 까지 옆에) = 값 사본(로직 · 자원 단가 줄) + 부름 닫힌 묶음 + SK 문서 · 끊음 · 따로 — `M02_Doc_Copy`(새)
  · 부름 문서 = 값 줄이 지금 M01 줄과 같으면 M01 수식 문서 · 다르면 만든 때 줄 변환
- SK 금액 = 문서 셈(`M02_Doc_Sk.on_run`) — 옛 칸(덮개 · 더한줄 …)은 파생(엔진에 넣던 고친 줄 → 문서 · 덮개 없는 바탕 = M01 문서 사본) · 수식 길 고침 = 따로(옛 칸 고침 저장 409 · 재생성 = 파생으로)
- 일위대가 · SC 로직 줄 = 문서 셈(`M02_Labor_Copy.line`) · 줄 옵션 = 문서 규칙(켜쌓기율 변수 r · 고른 자재 = 요소 키 · 별도계상 = 합밖 + 뒤 「재료비의 %」 밑엔 그 줄 금액 더함)
- [단가 갱신] = 자원 단가 줄만(문서 그대로) · [재생성] = 지금 M01 문서로(고친 문서 · 끊음 버림 알림) · 빈 SK = 빈 문서 「—」
- 수식 길 GET/POST …/sangeun/{키}/doc · /preview · /save(층 쓰기 · 서버 정본 셈) — `M02_Doc_Sk_Path` · `M02_Doc_Router`(새 · main 등록)
- B09 구조물 · 서고 사본 = `수식.json` 함께 굳힘(`M02_Doc_B09` · 원본판 안 듦) · B09 셈 그대로(문서 셈 전환 = 802-6)
- 옮기기 `M02_Doc_Migrate`(보기만 · --apply --backup · --restore) · 목록 `docs/raw/요구_1104/_목록_802-4a_M02대조.xlsx` — 층 SK 15 · 일위대가 21 · SC 4 · B09 서고 사본 3 같음 · 빈 SK 2 = 옛 0 → 「—」 · 가져온 식 1 = 802-4b
- 시험 test_802_4a(12) · test_204_2 서고 사본 파일 목록에 수식.json · test_795_1 `_fake` 가 M01 수식 문서도 같이 바꿈(M01 정본 = 수식 문서 — 옛 줄만 바꾼 흉내는 문서 사본에 안 닿음) · 옛 SK 흉내 = 사본 · 수식 둘 다 뗌

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:40:04 +09:00
eomsangdonandClaude Opus 5.5 54fb55487c feat(802-3): M01 단가산출 기초 수식 문서 화면 — 왼쪽 기호 식 · 오른쪽 값 식 · 빈 문서 시작 · 연결 고르기
- 공용 화면 A00_Common/formula_doc_*(M01 · 뒤에 M02 = 길 앞머리만 바꿈) — 옛 수식 나무 화면은 M01 에서 걷음(M02 는 그대로)
- 보이는 수식 그 자리 고침(공용 editEq · 전체 고르기 = 한 줄 글) · 분수 · 첨자 · 만약 = 탄 갈래만 · 반올림 자리 숨김 · 값 식 단위
- 값 식 숫자 누름 = 그 자리 값 · 기호 값 누름 = 정의 줄 · 정의 영역 = 기호 : 이름 — 연결(키 · 값 · 단위 없음 · 탄 갈래로 닿는 기호만)
- 새 기호 = 정의 필요 → 숫자 · 선택 · 입력 · 요소 · 표 · 단가산출 · 식 연결 · 기호 · 첨자 · 이름 고침 · 정의 지움
- 금액 = 경쟁사 꼴 이름 : 단가 × 수량 = 금액 · 묶음 하나 = 계 · 둘 이상 = 가. 이름 소계 + 합계 · 단가산출 연결 펼침 = 그 문서 식 · 값 식
- 셈 = 브라우저 계산기 즉시 · 연결 값 = 서버 기록 캐시(common_util_formula_doc_cache.ts ↔ master_doc_record.py) · 없는 연결만 뒤에서 미리 셈
- 서버 길 GET /api/m01/logic/doc · POST preview · save(서버가 정본으로 다시 셈 · 수식 문서 파일에 씀) · links · GET table (M01_MasterData_Doc.py)
- 연결 대상 안 고른 연결(표 · 요소 · 단가산출) = 연결 끊김 표지(파이썬 · TS 짝 가드 — 서버 500 걷음)
- 시험 test_802_3_doc_screen 11(읽기 · 빈 문서 · 저장 = 서버 다시 셈 · 관리자 아니면 막힘 · 지움 견딤 · 표지 갈래 · 후보)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:23:57 +09:00
eomsangdonandClaude Opus 5.5 d51c0b2200 test(791-1): M01 로직 지문 마스터 해시만 다시 뜸 — 801-5 단위 글로 해시 바뀜 · 로직 1,519 지문은 모두 그대로(한 줄만 바뀜)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:55:15 +09:00
eomsangdonandClaude Opus 5.5 9cf93a7488 chore(802-2): 메인 801-5 단위 글 받은 뒤 수식 문서 다시 지음 — 단위 글만 바뀜 · 대조 그대로(기본 1,519 같음 · 옵션 다름 1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:45:44 +09:00
eomsangdon 329dcaa61c Merge branch 'main_laptop_1' of C:/Program_coding/임도설계 및 견적자동화 프로그램 개발 into sub_laptop_3 2026-11-07 18:42:02 +09:00
eomsangdonandClaude Opus 5.5 154a044427 feat(802-2): 옛 M01 로직 1,519 → 수식 문서 변환 빌더 · 금액 전수 대조 · 같은 것만 저장 · 다름 목록
- 빌더 build_수식문서_802_2.py(다시 돌리면 같음 · --check) — 기호 = 수식 탭 기호(800-1 · 800-5) · 기본 옵션 = 수식 탭 기본값
- 대조 = 새 계산기 대 옛 엔진 노 · 재 · 경 · 계(결과) 원 단위: 기본 옵션 1,519/1,519 같음 · 옵션 표본 18,716 회(로직마다 고르기 갈래 모두) 중 같음 18,715 · 다름 1 · 둘 다 못 셈 134(옵션 바꾸면 표에 맞는 줄 없는 기계 작업량)
- 다름 1 = GC000100 지역=경북 재료비 1 원 — 식 속 단가산출 부름 값이 끝수 뒤(옛 엔진 = 끝수 전 날 값) · 보정 없음
- 저장 = 같은 것만 1,518 문서 — resources/master_data/수식문서/수식_<로직 파일 꼬리>.json 58 파일(옛 로직 JSON 안 씀)
- 목록 = docs/raw/요구_1104/_목록_802-2_변환대조.xlsx(다름 · 요약 · 둘 다 못 셈 · 변환 알림)
- 시험 test_802_2_convert 9(--check · 전수 기본 · 옵션 표본 모든 갈래 · 목록 · 저장본 다시 셈 · 791 실무 견본 넷 = 엑셀)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:41:50 +09:00
eomsangdonandClaude Opus 5.5 f7038cc280 feat(802-1): 단가산출 수식 문서 꼴 · 계산기(파이썬 · TS 짝) · 표지 · 꼴 문서
- 꼴: 변수표(기호 · 첨자 · 이름 · 단위 · 연결 = 숫자 · 선택 · 입력 · 요소 · 표 · 단가산출 · 식) + 식 줄 + 금액 줄 + 끝수 칸 — resources/master_data/_수식문서_틀.md
- 풀이 · 글로 되돌림 · 기호 · 이름 바꿈 전파 · 정적 검사 = master_doc.py ↔ common_util_formula_doc.ts
- 셈 = master_doc_calc.py ↔ common_util_formula_doc_calc.ts — 기호는 필요할 때 셈 · 정의 필요 · 순환 · 0 나눔 · 식 틀림 · 빈 문서 = 표지 + 금액 없음 · 값 없음 = 그 줄만 비움 · 묶음 소계 · 합계 · 줄 자리 · 끝전 큰나머지 · 계 끝수 · 조정 · 번지 · 단계 · 합밖
- M01 연결 풀이(표 찾기 · 요소 · 단가산출 문서) = master_doc_link.py ↔ common_util_formula_doc_table.ts(표 찾기) · 옛 엔진 셈 안 부름
- 옛 로직 변환 = master_doc_convert.py(802-2 빌더가 부름)
- 시험 test_802_1_formula_doc 48(꼴 · 셈 · 지움 견딤 300 넘는 지운 문서 예외 0 · 표지 · 거울 파이썬 ↔ TS 60 문서)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:41:39 +09:00
eomsangdon d1bd9d765a Merge branch 'sub_laptop_2' of C:/Aislo_wt/sub_laptop_2 into main_laptop_1 2026-11-07 18:01:57 +09:00
eomsangdonandClaude Sonnet 5.5 d7e6fe5cf8 feat(801-5): M01 로직 단위 글 건설품셈 원문 꼴 — 시간당 ㎥ · ㎡ → hr · 초 → sec · mm · cm · kg → ㎜ · ㎝ · ㎏ · TON · t → ton · km/hr → ㎞/hr
- 대응표 master_unit_text.RULES(규칙마다 원문 근거 절) · 빌더 build_단위표기_801_5(--check · --list · 셈 지문 전후 같을 때만 씀)
- M01 로직 53 개 · 칸 79 개 글만 바꿈(변수 입력 · 중간 단위 · 결과단위) · 계수 · 소요량 표 값칸 · 호표 줄 단위 · 원문이 두 꼴을 다 쓰는 단위(㎞ · km · 분 · min)는 안 건드림
- M02 사본 새로 씀 도구 M02_Copy_Units(M02_Copy_Symbols 의 fill 만 바꿔 끼움) — 사본 16 문서 28 칸 · 금액 전후 같음
- 검토 목록 docs/raw/요구_1104/_목록_801-5_단위표기.xlsx
- 옛 시험 기대 고침: test_796_3 굴착기 cm 단위 초 → sec(원문 표 머리 「싸이클시간(Sec)」)
- 새 test_801_5_units 6

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 18:00:30 +09:00
eomsangdonandClaude Opus 5.5 2376e70fbb revert(802-1): 801-1 wip 옛 나무 위 고침 걷음 — 요구 664 수식 문서로 다시 짬
- 걷음: 43784a4c0 의 옛 수식 나무 위 고침(서버 · 화면 · css) = 그 앞 판으로 되돌림
- 남김: master_equation_sign.py · ui_template_equation_text.ts · ui_template_equation_edit.ts(다시 쓸 조각 · 지금 안 부름) · text 는 `입력글` 을 제 타입에
- 지움: test_801_1_equation_inline.py — 걷은 옛 나무 고침을 재는 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:55:58 +09:00
eomsangdon 6c7618c54a Merge branch 'main_laptop_1' of C:/Program_coding/임도설계 및 견적자동화 프로그램 개발 into sub_laptop_3 2026-11-07 17:53:54 +09:00
eomsangdonandClaude Opus 5.5 b84d56f13b knowledge(요구665): 664 승인 · 요구서_664 수식 문서
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:53:11 +09:00
eomsangdonandClaude Opus 5.5 43784a4c09 wip(801-1): 수식 그 자리 고침 · 한 마디 줄 · 로직 부름 기호 · 반올림 · 안 탄 갈래 숨김 — 요구 664 로 멈춤(메인 안 합침)
- 서버: 한 마디 줄(표 찾기 · 요소 · 로직 부름 하나) = 줄 기호 · 「적용 · 값 · 기준」 칸 글 대신 조건 값 기호 · 로직 부름 기호(master_equation_sign) · 값 식 숫자 단위 · 있는 계산 변수 연결 바꿈 = 그 줄 식 · 식 속 원자 키보드 글(입력글)
- 화면: 박스 없는 조판 · 반올림 감싸개 · 안 탄 만약 숨김(감싸개 글로 구조 지킴) · 보이는 식 그 자리 고침(contenteditable · 원자 data-tok) · 값 숫자 그 자리 고침(valueEdit)
- 시험 test_801_1 7 통과 · 넓은 왕복 199 로직 중 13 걸림(식 300 자 상한 · 요소 EQ/MO 글 · 표 칸 Q 기호 맞물림) · test_796_2 옛 createFormulaCell 기대 안 고침(멈춤)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:50:36 +09:00
eomsangdonandClaude Opus 5.5 d257d7783c knowledge(요구664): 단가산출 기초 = 수식에서 시작 · 근본 방향 요구 원문
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:50:00 +09:00
eomsangdon 4baf69803e Merge branch 'main_laptop_1' of C:/Program_coding/임도설계 및 견적자동화 프로그램 개발 into sub_laptop_2 2026-11-07 17:47:48 +09:00
eomsangdonandClaude Sonnet 5.5 47f260e012 feat(801-3): 수식 탭 소계 · 합계 묶음 다시 — 돈 묶음 하나 = 머리 없음 · 계 / 둘 이상 = 소계 / 단가산출 둘 이상 = 합계 · 안쪽 말 0
- 묶음 한 곳 master_equation_group.bundles: 부른 · 더한 단가산출 하나씩 + 직접 줄(인력 · 재료 · 기계 · 덧줄) 한 묶음 · 돈 없는 줄 · 뺀 줄 = 묶음 아님 · 합계 밖 줄 = 밖 글
- M01 subtotals · M02 M02_Sangeun_Equation_Full 같은 꼴 · 끝수 셈 그대로 · 금액 전후 같음
- 부품 ui_template_equation_sum: 자기 자원 · 덧줄 글 0 · 묶음 하나 = 머리 없음 · 계 · 둘 이상 = 가. 머리 · 소 계 · 단가산출 둘 이상 = 합계
- 옛 시험 기대 고침: test_799_1(갈래 자원 · 덧줄 → 직접 한 묶음) · test_796_2c(줄마다 한 행 → 묶음 · 낱줄 · 밖) — 묶음 꼴이 바뀜
- 새 test_801_3_groups 5

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:44:49 +09:00
eomsangdonandClaude Opus 5.5 4185bf8baf knowledge(요구663): 만약 안 탄 갈래 · 원문 단위 표기 · 값 식 단위 요구 원문
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:39:21 +09:00
eomsangdon 90478a43c3 Merge branch 'sub_laptop_2' of C:/Aislo_wt/sub_laptop_2 into main_laptop_1 2026-11-07 17:31:51 +09:00
eomsangdonandClaude Opus 5.5 6fa3a3ce7b knowledge(요구662): 수식 그 자체 고침 · 묶음 다시 · 적용 · 반올림 요구 원문 · 그림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:29:35 +09:00
eomsangdonandClaude Sonnet 5.5 2a41b989a2 feat(800-5): 호표 줄 금액 참조 기호 — 정의 영역도 씌운 기호(C_합판) · M02 옛 사본 변수표 새로 쓰는 한 번 도구
- 정의 영역: 줄 참조 기호를 이름으로 덮던 줄 뺌 → 수식 영역과 같은 글(master_symbol 한 곳)
- M02_Copy_Symbols: 옛 사본 줄 변수표가 비었거나 열쇠 그대로면 지금 M01 줄 변수표로 채움 · 고친 기호 그대로 · 금액 대조 같을 때만 씀 · --backup
- test_800_5 새 4: 줄 참조 · M01 정의 한글(하위 로직 칩 빼고) 0 · 옛 사본 새로 씀 · 사용자 고침 보존 · 백업 되돌림

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:26:57 +09:00
eomsangdonandClaude Sonnet 5.5 bb5afa4125 chore(800-5): M02 마스터 층 옛 일위대가 · 사전조합 사본 변수표 새로 씀 — 전후 금액 다름 0
- M02_Copy_Symbols --apply --backup tmp/800_5 결과 중 git 추적 마스터 층 7 파일
- 개인 층은 storage(git 밖)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:26:54 +09:00
eomsangdonandClaude Opus 5.5 2732cf0f41 merge(800): sub2 800-1 변수 기호 기본값 · sub3 800-2~4 수식 영역 / 정의 영역 합침
- master_equation.export: 기호 씌움(msym.apply) 먼저 → 정의(view.definitions)는 기호 씌운 줄로 · _clean 마지막
- test_800_2: 800-1 기호로 중간 · 표 찾기가 다른 기호 = 다른 정의(옛 「한 줄로 합침」 기대 낡음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:17:13 +09:00
eomsangdonandClaude Sonnet 5.5 7191dbabcd feat(800-1): M01 로직 변수 기호 기본값 전부 — 입력 · 중간 6,664 칸 채움 + 찾기 · 요소 · 합 · 결과 기호 씌움
- master_symbol.py(이름 끝말 → 머리글자 규칙 · 아래첨자 · 갈래별 찾기 위 「표」 · 요소 P · w · 합 C · 결과 P · Q · 한 로직 안 겹침 숫자) + build_변수기호_800_1.py(--check · 채우기만 · 원문 796-3 · 사용자 고침 안 덮음)
- 로직 줄 변수표 6,664 칸(1,403 로직) · master_equation.export 끝 한 줄 msym.apply(보이기만 · 저장 안 함) · 셈 열쇠 · 식 · 값 그대로(1,519 로직 셈 지문 전후 같음)
- 검토 목록 xlsx 첫 시트 「기호 기본값(800-1)」 · 읽는 법(관례 줄 위 · 원문 줄 아래)
- test_800_1_var_symbols 8 · 지문 파일 마스터 해시만 갱신(결과 같음)
- 낡은 시험 고침: test_796_1(작업시간 T · 운반량 Q · 운반회수 n · 운반량 N 겹침은 기본 아래 비움) · test_796_2b(형틀목공 입력글 Q_형틀목공) — 기호 기본값이 생겨 기대 글이 바뀜

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:11:44 +09:00
eomsangdon 43c2bbb8a4 Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1 2026-11-07 17:10:32 +09:00
eomsangdonandClaude Sonnet 5.5 0254be9532 style(800-2): 수식 탭 정의 영역 폭 · 쌓임 기준 조정(M02 좁은 칸에서도 나란히)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:10:05 +09:00
eomsangdonandClaude Sonnet 5.5 59f0dd90a8 feat(800-4): 소계 · 합계를 기준 단가산출별 묶음으로 — 가. 이름 → 노 · 재 · 경 → 소 계 · 맨 끝 소 계(묶음 소계를 더한 식) · 블록 식 줄 아래 · 블록 줄 곁 열쇠 글 걷음
- 수식 영역: 블록 묶음은 그 블록 식 줄 바로 아래(createEqChain after) · 남은 묶음 · 소 계 · 끝수 · 합계는 맨 끝
- 금액 셈 그대로(보이기만) · 묶음 소계 합 + 끝수 = 합계 시험 추가(test_800_2)
- 정의 영역: 로직 부르기 정의의 이름 되풀이 걷음 · 상수 연결 설명

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 17:03:00 +09:00
eomsangdonandClaude Sonnet 5.5 f47991f698 feat(800-2): 수식 영역 / 변수 정의 영역 화면 · 식 바로 고침 · 새 기호 연결 지정(800-3)
- 수식 영역 줄 번호 · 구분선 · 줄마다 풀이 걷음 · 오른쪽 변수 정의 영역 부품 ui_template_equation_defs
- 식 칸을 눌러 그 자리 고침(줄 선택 · 아이콘 줄 걷음) · 틀린 식 그 자리 빨강 + 까닭 · 미리 셈
- 새 기호 = 정의 영역 맨 위 줄에서 연결(입력 · 표 · 요소 · 로직 · 상수) · 이름 · 기호 · 첨자 고침 · 지움(쓰는 변수 막음)
- 낡은 시험 test_796_2_equation_screen 두 건: 아이콘 줄 · 줄 선택 고침 → 바로 고침 꼴로 바꿈(동작 걷음)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 16:58:55 +09:00
eomsangdonandClaude Sonnet 5.5 a262b43e83 feat(800-2): 수식 변수 정의 영역 서버 — 정의 칸(안 탄 갈래 뺌 · 겹침 합침 · 열쇠 글 0) · 새 기호 연결 고침 · 후보 길
- 수식 응답 새 칸 정의 — 만약 탄 갈래 표지 걸림 그대로 · 같은 기호 한 줄 · 연결 사람 말 · 값 · 단위 · DB 열쇠 없음
- 고침 할일 연결(입력 · 표 · 요소 · 로직 · 상수) · 모르는 기호 까닭 · 후보 길 M01 · M02 equation/links
- 낡은 시험 test_796_1 「모르는 이름」 → 「모르는 기호」(까닭 문구 바뀜)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 16:46:39 +09:00
eomsangdon 5e72aff136 fix(est): apply head cost classification safely 2026-11-07 16:39:55 +09:00
eomsangdonandClaude Opus 5.5 f6405eaee4 knowledge(요구661): 소계 · 합계 기준 단가산출별 묶음 요구 원문 · 그림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 16:36:25 +09:00
eomsangdonandClaude Opus 5.5 972002417a knowledge(요구660): 수식 탭 추가 수정 요구 원문 · 그림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 16:35:54 +09:00
eomsangdon 9eae77d8f2 fix(est): apply selected estimate classification safely 2026-11-07 16:30:44 +09:00
eomsangdon c530ac694c feat(est): replace cost calculation text 2026-11-07 16:18:30 +09:00
eomsangdon cc1c1c659d fix(est): ignore stale Excel quantity selections 2026-11-07 16:13:02 +09:00
eomsangdon 132371f2f3 test(est): cover Korean quantity labels 2026-11-07 16:09:01 +09:00
eomsangdon 36589d1a36 test(est): verify Excel links in new projects 2026-11-07 16:08:02 +09:00
eomsangdon fb54335811 feat(est): preserve mismatched quantity results 2026-11-07 16:06:33 +09:00
eomsangdon 7d6d9769d5 fix(est): reserve deleted Excel link targets 2026-11-07 16:01:55 +09:00
eomsangdon 041d4e653e feat(est): exchange Excel quantity links 2026-11-07 15:59:24 +09:00
eomsangdonandClaude Opus 5.5 e4394d2cd8 chore(795-5): M02 마스터 층 옛 일위대가 · 구조물 조합에 로직 사본 찍음 · 요구 659 장부
- M02_Copy_Migrate --apply --backup tmp/795_5 결과 중 git 추적 마스터 층 7 파일(일위대가 6 · 조합 1)
- 전후 금액 다름 0 · 개인 층은 storage(git 밖)
- 사용자요구_1104 요구 659 원문

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 15:57:21 +09:00
eomsangdon fd6e2e6933 feat(est): persist and refresh Excel quantity links 2026-11-07 15:56:04 +09:00
eomsangdon f746154016 Merge branch 'sub_laptop_2' of C:/Aislo_wt/sub_laptop_2 into main_laptop_1 2026-11-07 15:55:19 +09:00
eomsangdonandClaude Sonnet 5.5 33655934f0 feat(796-3): M01 로직 변수 국문 이름 · 기호 · 첨자 · 단위 기본값 — 빌더로 채움 + 검토 목록
- 변수표_796_3.json(결정 자료 · 변수마다 근거 · 확신 원문 332 · 추정 223) + build_변수표_796_3.py(--check · 채우기만 · 사용자 고침 안 덮음)
- 로직 줄 변수표 555 칸(이름 442 · 기호 173 · 아래 첨자 90) · 빈 단위 24 칸 · 셈 열쇠 · 식 · 값 그대로(1,519 로직 셈 지문 전후 같음)
- 검토 목록 docs/raw/요구_1104/_목록_796-3_변수_이름기호단위.xlsx(추정 · 비움 줄 위)
- test_796_3_var_names 5 · 지문 파일 마스터 해시만 갱신(결과 같음)
- 낡은 시험 고침: test_796_1 M01 길 저장 — 샘플 로직에 기본 변수표가 생겨 「기존 칸 + 고친 칸」 으로 비교

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 15:54:55 +09:00
eomsangdon aac94d6508 feat(est): apply quantity from an Excel cell 2026-11-07 15:49:57 +09:00
eomsangdonandClaude Sonnet 5.5 c47e6dc832 feat(796-2e-2): 더한 기초 블록 [연결 끊기] · [다시 잇기] 화면 — 수식 탭 소계 블록 머리 상태 · 단추 · 끊은 블록 속 수식 고침
- 소계 표 부품에 덧단추 자리(EqSumHooks head · body) · EqPage.sum 으로 페이지가 끼움
- M02 수식 탭: 블록 머리 「연결」 · 「끊음」 + [연결 끊기] · [다시 잇기](확인창) · 끊은 블록 속 = 같은 수식 부품 · [단가 갱신] · [재생성] 보고에 끊은 기초 한 줄
- test_799_1_subtotals 낡은 문자열 비교(createEqSum(eq, open) 통째)를 인자 앞부분 비교로 고침 — 부품 호출에 덧단추 인자가 붙어서

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 15:47:37 +09:00
eomsangdon c788960bf2 fix(est): create quantity sheets on remote HTTP 2026-11-07 15:44:05 +09:00
eomsangdon f6e7095e3e test(est): cover combined quantity edits 2026-11-07 15:41:32 +09:00
eomsangdon d02fa37723 fix(est): preserve quantity results on label changes 2026-11-07 15:39:23 +09:00
eomsangdonandClaude Sonnet 5.5 be43dad64e feat(796-2e-1): 더한 기초 블록 [연결 끊기] · [다시 잇기] 서버 — 끊음 = 이 SK 안 자기 사본 줄 · 수식 고침 · 재생성 때 줄 안 바꿈
- M02_Sangeun_Block 신설: 끊기(닫힌 묶음 굳힘 · 금액 그대로) · 다시 잇기(고침 버림 + 원본 갈음) · 끊은 기초 수식 읽기 · 미리 셈 · 저장 검사
- Calc.sub_run 이 끊은 기초 자기 줄 + 수식 고침을 얹어 셈 · Copy.settle 이 다시 잇기 표지 처리 · [단가 갱신] · [재생성] 응답 끊은기초 한 줄
- Router: block/unlink · relink · equation 몸 블록 · 새 시험 test_796_2e_unlink_basis 6

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 15:38:36 +09:00
eomsangdon 8d4d23e783 feat(est): save quantity item labels in ESTX 2026-11-07 15:37:20 +09:00
eomsangdon 1d1f27bdff feat(est): configure separate print margins 2026-11-07 15:33:06 +09:00
eomsangdon 7d9837a081 feat(est): locate cost formula errors 2026-11-07 15:29:31 +09:00
eomsangdonandClaude Sonnet 5.5 e171dda4e3 feat(797-2): 엑셀 불러오기 소요량 · 계수 이음 — 값 역색인 후보 + 사람 확인 · 고름 = 표 칸 참조 기록
- 새 `M02_Sangeun_QtyLinks` — 가져온 식 줄(호표 수량 · 덧줄 식)의 수를 M01 소요량(QC/QF) · 계수(CC/CF) 칸과 값 역색인(`San_Basis_Index.lookup`)으로 맞춤 · 같은 값 + 같은 단위 성격 + 공종 문맥(인력 = 직종 · 재료 = 칸 이름 추가) · 일반 수 · 비교 글자 · 중간 변수는 자리 아님 · 유일해도 자동 확정 없음
- 고름 = `가져온.연결` 기록(층 M01 · 표 · 줄 · 칸 · 값 · 꼴) · 엑셀 수 = 칸 값일 때만(다르면 400 까닭) · 버림 · 되돌림 · 기록만(셈 · 금액 · 단가 갱신 안 건드림) · 저장 거름(`clean`) = 자리 · 수 맞는 기록만
- 길 `…/sangeun/imported/{qty|qty-pick|qty-drop}` · 가져온 식 만들 때 표지 「참고 · 소요량 후보 N · 사람 확인」 · 「표 칸 없음」(못 옮김 표지 아님)
- 화면 — 가져온 식 모달 머리 판 한 줄(집계 · [이음 보기]) + 모달(줄마다 후보 · [고르기] · [풀기] · [버리기] · [되돌리기] · 부품 상자만)
- 시험 test_797_2_qty_coef_links 9 · 낡은 시험 없음

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 15:23:12 +09:00
eomsangdon ceb62d65c5 Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1 2026-11-07 14:34:06 +09:00
eomsangdonandClaude Sonnet 5.5 709880953f test(743-1): 불러오기 잊기 시험 — 실무자료 안 HP 파일 수에 산근 시트(793-3b)를 셈
797-1 탓 아님 — 올림 때 산근 시트(`실무자료/산근/HP-*.json`)를 두는 것은 793-3b 부터이고 `_files` 가 하위까지 훑어 둘로 센 옛 기대가 낡음. 기대를 셋(HP · 격자 · 산근)으로 고침.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 14:33:50 +09:00
eomsangdon 319b0798a6 Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1 2026-11-07 14:17:28 +09:00
eomsangdonandClaude Sonnet 5.5 430aaa9aae feat(797-1): 엑셀 불러오기 요소 연결 — 기계 이음 [우리 단가로] 셈 · 유가 · 환율 이음 · 산근 속 산근 형제 참조 · 읽기 불가 사유 응답
- 기계(EQ) 이음 줄 [우리 단가로] = 시간당 기계경비의 그 비목(옛 Decimal × dict 실패 387 블록 풀림) · 시간당 자료 없는 EQ = 엑셀 단가 그대로
- 내보내기 `_m01` = 유가(MO) · 환율(FX) 유일 짝만 자동 · 여럿 = 표지 · 화폐는 이름 그대로일 때만(「유로폼」 오판 막음)
- 산근 속 산근 = 같은 엑셀 안쪽 블록을 형제 가져온 식 SK 로 먼저 만들어 `참조`(비목 한 줄 부름 = `참조비목`) · 단가기준 따라감 · 못 잇는 줄 = 얼림 + 표지
- 쓰는 곳 · 층 복사 · 프로젝트 굳힘이 가져온 호표 참조를 봄(`Labor_Ref.refs_in` · `_move_doc`)
- 올림 응답 `읽기불가` + 화면 토스트
- 시험 test_797_1_import_links 9 · 낡은 시험 없음

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 14:17:06 +09:00
eomsangdonandClaude Sonnet 5.5 cf411c5f0a feat(796-2d): 단가산출 상세 탭 소계 · 합계가 합밖 · 제외 단계 줄을 안 더함 — 엔진 줄 표지 합밖 · 상세 줄 합 밖 배지 · 박스 소계 · 합계 표지 줄 뺌(금액 셈 그대로)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 13:38:54 +09:00
eomsangdonandClaude Sonnet 5.5 137cfb701d feat(796-2c): M02 수식 탭이 SK 셈 답 전체를 그림 — 더한 기초 · 뺀줄 · 덮개 · 율 줄 · 합밖 소계표 · 합계 = SK 금액 · 고친 칸 표지
- 서버 M02_Sangeun_Equation_Full(새 파일) — 합계 = SK 셈 답 기준 · 소계 = 셈 답 줄마다 한 행(바탕 · 덮음 · 더함 · 뺌 · 합밖) + 끝수 · 더한 기초 수식 나무 · 줄 표지(식 · 상수 · 표값 · 품 · 단가 · 변수줄 · 뺌 + 원래 → 지금)
- M02_Sangeun_Equation — read · preview 가 얹음(전 = 고침 없는 SK 금액) · 수식 입력 = 자동값(SK 셈과 같은 입력)
- 부품 createEqSum(tree, open) 상태 알약 · 원래 · 접힘 기초 수식 · 줄 표지 · 흐림 CSS
- 시험 test_796_2c(견본 넷) · 낡은 시험 정리 — test_792_1 TS 거울 시험(799-2 에 걷은 모델 파일 읽음) 지움 · test_799_1 호출 글 갱신
- SK 금액 전후 같음 · M01 수식 탭 그대로

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 13:24:35 +09:00
eomsangdon 94c7d29b11 fix(est): name copied projects when saving 2026-11-07 13:18:48 +09:00
eomsangdon 07a292a009 feat(est): open backups and support remote creation 2026-11-07 13:16:11 +09:00
eomsangdonandClaude Sonnet 5.5 c9bdf6d891 feat(799-2): M01 로직 상세 · M02 단가산출 상세 옛 탭 넷(시트 · 인력/재료/경비 · 시험 계산 · 텍스트 수식) 걷음 — 보기는 수식 · 단가산출 상세 · 셈 구동 따로
- 셈 구동 떼기: 새 M01_MasterData_UI_Logic_Drive.ts(driveRun · previewText · autoFill · createDrive) — M01 상세는 숨은 시험 계산 칸 단추 대신 구동 함수 · 시험 계산 칸(Logic_Calc)은 로직 복사 화면이 쓰므로 남기고 같은 구동을 부름 · 재료 고름은 rerun 고리
- M02: 새 _Sangeun_Tabs.ts(수식 · 단가산출 상세 보기 고르기) · Detail 의 시험 계산 · 인력/재료/경비 걷음
- 걷은 파일(git rm): M01 LogicLab_Sheet · Logic_Sheet_Model · LogicLab_Formula(.ts .css) · FormulaTokens · LogicLab_Est / M02 Sangeun_Sheet(.ts .css) · SheetEdit · SheetFormula · logic Sheet_Model · Const_Model · Formula_Model · 부품 시트 줄(createCalcSheetRow) · Cover_Model.sheetGroups · Calc 의 M02 고리(answer · shown · held) · 죽은 CSS · 글
- 서버 길 · 문서 칸(변수줄 · 글줄 · 덮개)은 남김 — 금액 그대로
- 낡은 시험: test_790_1(시트 화면 전부) · test_714_3 · test_748_2(+ helper)는 화면이 없어져 지움 · test_794_1 · test_790_2 는 화면 · TS 거울만 덜고 서버 시험 남김 · test_714_2 · 717_2b · 721 · 722(원래 틀린 한 줄 고침) · 734 · 737 · 750_2 · 793_3b · 796_2 · 796_2b 는 보기 둘 · 새 파일에 맞춤 · 새 test_799_2_old_tabs_gone

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 12:57:30 +09:00
eomsangdonandClaude Sonnet 5.5 d13b5b1753 feat(799-1): 수식 탭 단가산출 소계 · 끝수 · 합계 — 블록마다 노 · 재 · 경 소계 보이기만(금액 그대로)
- 서버 master_equation.subtotals — 호표 줄(하위 로직 블록 한 줄씩 · 자원 · 덧줄)의 버린 줄 비목 + 끝수(합계 − 줄 합)
- 부품 ui_template_equation_sum — 줄 이음 아래 소계 표(M01 · M02 수식 탭 같이)
- 시험 test_799_1_subtotals — 줄 합 + 끝수 = 합계 · 합계 = 엔진 끝수 입힌 비목 합(전후 같음)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 12:29:26 +09:00
eomsangdon b608d2aa3d Merge branch 'sub_laptop_2' of C:/Aislo_wt/sub_laptop_2 into main_laptop_1 2026-11-07 12:22:18 +09:00
eomsangdonandClaude Opus 5.5 0959ec3c98 feat(796-2b): M02 단가산출 「수식」 첫 탭 — M01 수식 탭 부품 다시 씀(페이지 갈래) · 사본 [단가 갱신] · [재생성] · M01 새 판 배지 · 엑셀 원래
- M01 수식 탭 부품에 `page` 갈래 — 길 · 고침 목록 · 옵션 값을 페이지가 줌 · 저장 = 페이지 [저장] · M01 동작 그대로(판 바뀌면 다시 셈만 더함)
- 새 M02_MasterTemplete_Sangeun_Equation — 문서 `수식고침` 캐시 · 사본 표지 줄 · 가져온 식 「엑셀 원래」 · 일위대가 줄 모달 = 보기만
- 단가산출 상세 보기 = 수식(첫 · 기본) · 단가산출 상세 · 시트 · 옛 셋 뒤로(걷지 않음) · [저장] 뒤 할일 칸 뗌
- 낡은 시험 test_796_2 — 길 글이 앞머리 + 갈래로 바뀜(`/logic/equation${suffix}`) → 고침 · 화면 지킴 시험 하나 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 12:20:39 +09:00
eomsangdonandClaude Sonnet 5.5 29e60a3ad6 feat(798-1): M01 요율 조달청 제비율 내장 엑셀 — 빌더 · 읽기 전용 화면 · RT 줄 → 칸 이동
- build_제비율책.py: 조달청 xlsx → 내장 엑셀 책 JSON(병합 · 채움 · 글꼴 · 테두리 · 열 폭 · 행 높이 · 시트 둘 · 수식 결과) · --check(판 바뀜 · RT 칸 = 정본 값)
- 제비율엑셀/조달청_제비율_책.json · /api/m01/rate-sheet · 요율 화면 위 엑셀 + 아래 RT 표 · 확대 · 축소
- 정본 요율_조달청제비율.json · B09 요율 셈 그대로

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 12:20:32 +09:00
eomsangdon c48580d6c9 Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1 2026-11-07 12:07:59 +09:00
eomsangdonandClaude Sonnet 5.5 0792a17e2c feat(797-1a): 엑셀 불러오기 빠진 정보 표지 다섯 + 줄 단위 합격 시험 · 표본 밖 고정
- 금액 줄 못 읽음: 결과 뒤 꼬리 글은 떼고 금액 줄로 읽음(2016 틀린 줄 2 → 0) · 못 읽는 글끝 · 식못읽음 = 표지 + 보인 값 보존
- 단가 후보 여럿 · 사람 확인 / 추정(문맥 · 기계 세 줄) 표지 — 임의 선택 대신 표지
- 당초 / 적용 칸 표지(Book.applied_how) · 조정 비율 · 낙찰률 미지원 표지
- FromHopyo.keep 이 읽기 불가 사유(빈 파일 · DRM 암호 · 형식 오류)를 돌려줌
- 사람 확인 표지는 「참고 · 」 머리 — 못 옮김 표지와 가름 · test_793_2 는 못 옮김 표지만 셈(낡은 기대 수정)
- test_797_1a 신설: 작은 견본 · 표지 없는 틀린 줄 0 · 표본 밖 수목 142 · 대흥공통 46 · 2016 374 고정 · 버림1 일치율 감시
- 금액 결과 그대로(엔진 · 수식 · Copy 손대지 않음)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 12:07:36 +09:00
eomsangdonandClaude Opus 5.5 f02c39c98a feat(796-2b): M02 단가산출 수식 서버 길 — 사본 줄 수식 읽기 · 고침 미리 셈 · 사본 위 고침 기록(수식고침) · [저장] 정본 셈
- 새 M02_Sangeun_Equation — 문서 덧칸 `수식고침`(M01 수식 고침 꼴 + 까닭) = 사본 위 기록 · 사본은 찍은 그대로(원래 대비)
- 셈 = _base_of 가 바탕 줄(사본 · 가져온 식)에 얹음 → 모든 셈 길 한 벌 · 못 얹음 = 짝잃음 「수식」 · 저장 = 400
- 길 POST …/sangeun/equation(층 읽기) · /equation/preview(층 쓰기 · 저장 없음) · 저장 = SK [저장]
- 가져온 식(SK 키) = 잠깐 키 GX999997 로 엔진에 얹음 · 사본 색인 Copy 얕은 사본(__copy__)
- 원래(sheet) = 수식고침 뺀 셈 · 수식고침 SK 펼치지 않음 · 더한 입력 이름 입력값에 남김
- 시험 test_796_2b_m02_equation 6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 12:05:48 +09:00
eomsangdon 04b5592091 Merge branch 'sub_laptop_2' of C:/Aislo_wt/sub_laptop_2 into main_laptop_1 2026-11-07 11:54:38 +09:00
eomsangdonandClaude Opus 5.5 3e1dc294b2 feat(795-4): B09 사본 규칙 — 만든 때 그대로 · [단가 갱신] 값만 · [재생성] 따로 · 시점 섞임 고침
- 구조물 · 서고 사본 단가 묶음 = M02 일위대가 · SC · SK 로직 사본 줄 먼저(`collect(over)`) · M01 에서 지워진 키 = `사본_남은줄.json`
- 단가 책 새 줄([저장] · 첫 굳힘) = 연결 사본 `단가.json` 자원 줄(구조물 지정 때 값 · `Linked.frozen_rows`) · 없는 자원만 지금 M01 + 책 `now_keys` · 입력 `now` · 알림 한 줄
- POST …/estimation/prices 기본 = 단가 갱신(값만 · 사본 재적용 없음) · job 재생성 = 옛 [업데이트] · 미리 보기 단가 갱신 = 바뀐 자원 · 총괄 · 대가 합 전 → 후
- B09 화면 단추 [업데이트] → [단가 갱신] · 미리 보기 창 [재생성] → 재생성 미리 보기 → [적용] · 안내 글 둘 이름 맞춤
- 옮기기 도구(보기만) — 프로젝트 층 못셈 14 = 「못셈(옛 SP 참조)」 갈래 · B09 구조물 · 서고 사본 대조(그대로 · 재생성 금액)
- 새 시험 test_795_4_b09_copy(8) · 낡은 시험 고침: test_203_1 · test_296 = 단추 이름 「업데이트」 · 호버 글 · 몸 `{ user }` 를 박아 둔 것 → 795-4 이름 · job 덧칸으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:54:10 +09:00
eomsangdon 02bd87bb4e Merge branch 'main_laptop_1' of C:/Program_coding/임도설계 및 견적자동화 프로그램 개발 into sub_laptop_3 2026-11-07 11:46:34 +09:00
eomsangdonandClaude Sonnet 5.5 eaadb48a2b feat(796-2a): 수식 탭 화면 — 공통 수식 부품 · M01 수식 탭(첫 탭) · 고침 · 변수 창 · 옵션 가로
- ui_template_equation(.ts · _line.ts · .css): 분수 · 첨자 · 괄호 · % · 조건 칩 · 하위 로직 블록(접힘) · 결과명 = 이름 식 = 값 식 = 결과 + 변수 풀이 · 부품 모음 견본 한 줄
- M01 LogicLab_Equation: 옵션 가로(도구줄 + 값 칸 · 찾기형 · 말줄임) · 줄 고르면 식 칸 + 숨은 아이콘 줄 · 서버 미리 셈 다시 그림 · 고침 목록 캐시 → [수식 저장](개정 확인) · 떠나기 전 버림 물음
- 변수 창(이름 · 기호 · 위 · 아래첨자 · 단위 · 값 · 보이는 자리) · 변수 더함 · 지움(쓰는 곳 있으면 서버 까닭 그대로)
- 보기 고르기 = 수식 첫 탭 · 단가산출 상세 · 시트 · 인력재료경비 · 시험계산 · 텍스트 수식은 뒤로(걷지 않음)
- test_796_2_equation_screen 새로 · test_794_1 기대 갱신(첫 탭 수식 · LogicLab 700 줄 상한 — 수식 이음 14 줄)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 11:46:28 +09:00
eomsangdon 2bcb8bf9b9 feat(est): allow access on configured network address 2026-11-07 11:37:13 +09:00
eomsangdon 600d7b0ab1 feat(est): show standard price design changes 2026-11-07 11:35:02 +09:00
eomsangdonandClaude Opus 5.5 16d7eede50 feat(795-3): 옛 SK · SC · 일위대가 사본 옮기기 도구 — 전후 금액 대조 · 같음만 씀 · 백업 · 되돌림
- 새 M02_Copy_Migrate — 사본 없는 마스터 · 회사 · 개인 층 문서에 지금 M01 사본 찍어 대조(SK 기준 · SC · 일위대가 기준합 · 반영합)
- 같음 = 사본 칸만 더해 씀 · 다름 · 못 셈 = 안 씀(목록) · 프로젝트 층 일위대가 = 세기 · 대조만(795-4)
- --apply 는 --backup 폴더 필수 · --restore 로 다시 덮음 · 기본 = 보기만(표)
- 실제 storage 보기만 결과 — 다름 0 · 층 문서 같음 41 · 필요없음 6 · 프로젝트 같음 70 · 못셈 14(옛 SP 참조) · 옮기지 않음
- 시험 test_795_3_migrate 3(임시 storage)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:32:22 +09:00
eomsangdonandClaude Opus 5.5 cc259d1121 feat(795-2): 일위대가 · 사전조합 로직 사본 — 저장 때 찍음 · [단가 갱신] · [재생성] · 줄 덮개 다시 얹힘
- 새 M02_Labor_Copy — SK 사본(795-1) 한 벌 · 담은로직 로직 줄 · 줄 덮개 더한줄 · 고정 이음 · 자재 MT 가 닿는 M01 닫힌 묶음
- 셈 = Labor_Doc.calc 층 셈 첫머리 사본 덮개 · SC 1 단위 값(Expand) = 그 SC 사본 · 프로젝트 층 · B09 = 옛 길(795-4)
- 사본 정함 = 받은 사본 먼저(열 복사 · 층 복사 = 원본 사본 그대로) → 저장본 → [저장] 때 찍음 · 새 줄만 더함
- 길 POST …/labor/{열 | SC 키}/copy/price|regen · 셈 답 덧칸 사본 표지 · 같은 내용 지문 SC 사본 몫
- Sangeun_Copy 찍기 갈래 나눔(reach · snap · grow) · SK 더한줄 인력 · 재료 · 기계 요소도 사본에
- Labor_Doc 줄 수 그대로(686) · 시험 test_795_2_sc_copy 6 · 기존 일위대가 · SC · SK 시험 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:32:15 +09:00
eomsangdon f915e0c33b feat(est): inspect standard price usage 2026-11-07 11:31:12 +09:00
eomsangdon ba95a6d606 fix(est): preserve notes on added report forms 2026-11-07 11:27:41 +09:00
eomsangdon fb460cd0a3 fix(est): keep progress round names unique 2026-11-07 11:25:41 +09:00
eomsangdon 20ef5a6a7f fix(est): reject oversized attachments early 2026-11-07 11:23:39 +09:00
eomsangdon d2a8889475 fix(est): reject duplicate quantity sheet codes 2026-11-07 11:19:28 +09:00
eomsangdon cb0a4fbfec feat(est): clear estimate quantities 2026-11-07 11:17:58 +09:00
eomsangdon acf6b33921 Merge branch 'sub_laptop_2' of C:/Aislo_wt/sub_laptop_2 into main_laptop_1 2026-11-07 11:17:15 +09:00
eomsangdonandClaude Opus 5.5 5112bc1425 feat(795-1): SK 로직 사본 — 저장 때 M01 닫힌 묶음 찍음 · [단가 갱신] · [재생성] · M01 새 판 배지
- 새 M02_Sangeun_Copy — 사본 = 로직 · 하위 로직 · 찾기 표 · 그 밖 요소(로직 갈래) + 자원 단가 LB · MT · EQ · MO · FX(단가 기준 시점 갈래)
- 셈 = M01 색인 위 사본 줄 덮개(Copy) · Calc 바탕 줄 · run · 저장 · 셈 길 첫머리만 고리(줄 수 그대로)
- 사본은 서버가 정함(저장본 사본 · 없으면 받은 사본 · 그것도 없으면 저장 때 찍음) · 할일 칸 사본할일 = [저장] 때 적용 · 자동 갱신 없음
- 지워진 M01 키 = 사본 마지막 값 · 표지 지운키 · 사본 없는 옛 SK = 옛 길
- 같은 내용 지문에 사본 몫(지금 M01 과 다른 판만) · 길 POST …/sangeun/copy/price|regen
- 시험 test_795_1_sk_copy 6 · 기존 SK · 지문 시험 294 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:16:39 +09:00
eomsangdon 46b3c93f66 feat(est): reset all progress rounds 2026-11-07 11:15:31 +09:00
eomsangdonandClaude Opus 5.5 771e89c983 feat(796-1): 수식 모델 — 엔진 로직 ↔ 수식 나무 양방향 · 변수 표 · 추가 · 삭제 · 이름 전파 · 검사 · M01 길
- scripts/master_equation.py: 로직 줄 → 그림용 나무(이름식 · 값식 · 분수 · 퍼센트 · 조건 · 블록) · 풀이 · 변수 표 · 원문 「여기서」 기호 후보
- scripts/master_equation_edit.py: 나무 · 키보드 한 줄 식 → 엔진 식 · 더함 · 지움(쓰는 곳 막음) · 이름 · 기호 · 첨자(변수표 칸 · 식 그대로) · 고친 중간 줄 표시 4 자리 · 검사
- M01 길 셋(/logic/equation · preview · save — 기존 /save · 개정확인) · M02 사본 이음 export_row · edit_row
- _틀.md 로직 열 차례 끝 변수표 · 셈 값 바뀜 0(791-1 지문 같음)
- 시험 test_796_1_formula_model.py 17

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:14:25 +09:00
eomsangdon 083ad3b46b feat(est): manage local project attachments 2026-11-07 11:13:59 +09:00
eomsangdon 2cf1f17c2a feat(est): set precision on selected rows 2026-11-07 11:08:48 +09:00
eomsangdon 539aa04712 fix(est): avoid duplicate copied group codes 2026-11-07 11:06:05 +09:00
eomsangdon 83d3d43f10 feat(est): apply all quantity sheets 2026-11-07 11:04:23 +09:00
eomsangdon b6a3392bc4 feat(est): add data issue search 2026-11-07 11:02:20 +09:00
eomsangdon ad01861c48 feat(stmate-test): 100% COSTN.SMT interpreter, 5-tier X/D/B/BOQ/Kanjub cascade engine, 30-sheet reports & 6-sheet Excel/XML export 2026-11-07 11:01:37 +09:00
eomsangdon db02fa9994 feat(est): edit review summary in workflow 2026-11-07 10:56:50 +09:00
eomsangdonandClaude Opus 5.5 785f5793a7 knowledge(요구): 분석 658 — 경쟁사 분석 · 분석 엔진 검토(근거 · 과적합 · 표본 밖 · 빠진 정보 처리 · 고칠 목록)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:56:13 +09:00
eomsangdon cfe78e2e3b fix(est): guard invalid contract review totals 2026-11-07 10:54:02 +09:00
eomsangdon 12950205f6 feat(est): reorder manual quantity formulas 2026-11-07 10:51:42 +09:00
eomsangdon 0098ba58e2 feat(est): apply progress to schedule 2026-11-07 10:50:03 +09:00
eomsangdon 078fe9034f feat(est): export report results to Excel 2026-11-07 10:47:39 +09:00
eomsangdonandClaude Opus 5.5 a496ca3832 knowledge(요구): 요구서 655 — 자릿수 일단 반영 · 엑셀 누락 주의 · 경쟁사 분석 · 엔진 검토 절
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:45:31 +09:00
eomsangdonandClaude Opus 5.5 cb78132f10 knowledge(요구): 요구 658 — 자릿수 일단 반영 · 엑셀 누락 정보 주의 · 경쟁사 분석 · 분석 엔진 검토
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:45:17 +09:00
eomsangdon 9787b4b1f6 feat(est): print annual item allocations 2026-11-07 10:42:16 +09:00
eomsangdon 7422a70ac4 fix(est): match repeated escalation items 2026-11-07 10:40:30 +09:00
eomsangdon c1a4b9a178 fix(est): reject ambiguous progress inputs 2026-11-07 10:38:36 +09:00
eomsangdon 448b92276f fix(est): preserve unmatched progress exclusions 2026-11-07 10:36:30 +09:00
eomsangdonandClaude Opus 5.5 aea492c0d7 knowledge(요구): 요구서 655 고침 — 핵심 원칙(수식 = 계산 로직 · 변수 추가 삭제 · 이름 전파) · 단계 차례 바탕 → 수식 → 둘레
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:36:27 +09:00
eomsangdonandClaude Opus 5.5 d870615279 knowledge(요구): 요구 657 — 새 요구서 · 계획서 · 수식 고침 = 계산 로직 바뀜 · 변수 추가 삭제 · 코드는 요청 때만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:35:49 +09:00
eomsangdonandClaude Opus 5.5 600fd82241 knowledge(요구): 요구서 655 — 단가산출 수식 탭 · 로직 사본 · 불러오기 요소 연결 · 제비율 엑셀(확정 · 정함 · 물결 1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:35:27 +09:00
eomsangdonandClaude Opus 5.5 899ebd64f9 knowledge(요구): 요구 656 — 값 갱신 경쟁사 동일 · B09 필요하면 값만 · 기호 원문 꼴 · 자동갱신 없음 · 단가산출 상세 탭 둠
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:34:52 +09:00
eomsangdon 5302684c93 fix(est): guard cross-table row paste 2026-11-07 10:34:40 +09:00
eomsangdon 4f33cafb12 feat(est): move estimate rows safely 2026-11-07 10:33:22 +09:00
eomsangdon 0f635cef0f fix(est): distinguish repeated change items 2026-11-07 10:31:12 +09:00
eomsangdon eb3dba0e9e feat(est): compare design change cost items 2026-11-07 10:29:31 +09:00
eomsangdon 5f9b5432ec feat(est): print progress and adjustment costs 2026-11-07 10:27:09 +09:00
eomsangdonandClaude Opus 5.5 7d6c9f3244 knowledge(요구): 분석 655-B — 수식 탭(PPT 수식 꼴) · 여러 단계 · 옵션 가로 · 제비율 내장 엑셀
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:25:48 +09:00
eomsangdonandClaude Opus 5.5 af80736561 knowledge(요구): 분석 655-A — 로직 사본 · 복수 단가산출 소계 · 불러오기 값 연결(기계 이음 312 블록 흠 포함)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:24:52 +09:00
eomsangdonandClaude Opus 5.5 e6d36c3cd7 knowledge(요구): 요구 655 — 이슈관리대장 엄상돈 탭 의견 원문 · 수식 표현 예시 그림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:11:03 +09:00
eomsangdon 69eca5b224 auto: 2026-11-07 09:36 (ESD_LAPTOP) 2026-11-07 09:36:37 +09:00
eomsangdonandClaude Opus 5.5 a8eaaca2db knowledge(요구): 분석 654-B — STmate 단가산출 DB 구성 · 분리 방식
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 09:02:27 +09:00
eomsangdonandClaude Opus 5.5 2ccd151728 knowledge(요구): 분석 654-A — EST 단가산출 DB 구성 · 기초와 사용자 자료 분리 방식
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 09:01:16 +09:00
eomsangdon e4b7322bb6 feat(est): exchange price adjustment inputs 2026-11-07 08:53:42 +09:00
eomsangdonandClaude Opus 5.5 23f2a6f6a9 knowledge(요구): 요구 654 — 코드 작업 중지 · 방향 다시 세움 · 경쟁사 단가산출 기본 DB 구성 확인 · 케이브맨 중지
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 08:50:31 +09:00
eomsangdon 95c7acb7d4 feat(est): print annual allocations 2026-11-07 08:49:33 +09:00
eomsangdon cffeb84ce4 feat(est): print progress and price adjustments 2026-11-07 08:48:25 +09:00
eomsangdon b3bd093d42 fix(est): preserve fixed review amounts 2026-11-07 08:45:49 +09:00
eomsangdon 3a10256e69 feat(est): reset individual review entries 2026-11-07 08:30:16 +09:00
eomsangdon 35d3cc936c fix(est): validate imported review targets 2026-11-07 08:28:29 +09:00
eomsangdon a0461bf042 fix(est): retain legacy quantity links in Excel 2026-11-07 08:25:13 +09:00
eomsangdon bc41c8716f feat(est): exchange annual and draft progress 2026-11-07 08:24:00 +09:00
eomsangdon 50d66fae78 feat(est): exchange contract review inputs 2026-11-07 08:20:03 +09:00
eomsangdon 2f20e29d31 feat(est): record contract review reasons 2026-11-07 08:18:19 +09:00
eomsangdon 1d02b0205a fix(est): block missing quantity markers 2026-11-07 08:14:32 +09:00
eomsangdon ab5a07ff34 feat(est): exchange quantity documents in Excel 2026-11-07 08:12:27 +09:00
eomsangdon a8c20c3f19 feat(est): exchange quantity and schedule sheets 2026-11-07 08:07:11 +09:00
eomsangdon f20cbe44d1 feat(est): print contract review summary 2026-11-07 08:00:37 +09:00
eomsangdon 922c9a05a1 feat(est): add contract review workflow 2026-11-07 07:59:24 +09:00
eomsangdon 4e50e2fe94 feat(est): reorder annual work stages 2026-11-07 07:53:54 +09:00
eomsangdon b279b964ce feat(est): link quantity to individual rows 2026-11-07 07:51:42 +09:00
eomsangdon b6d98ac9d5 feat(est): print quantity formulas and results 2026-11-07 07:47:58 +09:00
eomsangdon 9380080afe fix(est): show changes without item codes 2026-11-07 07:46:09 +09:00
eomsangdon 19d7093050 fix(est): avoid reused quantity references 2026-11-07 07:44:29 +09:00
eomsangdon 8c1da2c29e feat(est): populate schedule from estimates 2026-11-07 07:42:34 +09:00
eomsangdon 4c6bb9d70a fix(est): apply selected quantity calculations 2026-11-07 07:37:40 +09:00
eomsangdon 864ff1f55b feat(est): manage quantity calculation sheets 2026-11-07 07:33:57 +09:00
eomsangdon 6cea0c97f2 fix(est): reject reserved annual names 2026-11-07 02:43:36 +09:00
eomsangdon 042d574fee test(est): cover original text style boundary 2026-11-07 02:41:05 +09:00
eomsangdon 19097b7255 fix(est): match prior unnamed cost codes 2026-11-07 02:38:42 +09:00
eomsangdon c03929e369 fix(est): combine quantity text edits 2026-11-07 02:35:12 +09:00
eomsangdon 170086a5b1 fix(est): reject ambiguous quantity targets 2026-11-07 02:33:07 +09:00
eomsangdon 06d47eda29 feat(est): save edits across text styles 2026-11-07 02:29:39 +09:00
eomsangdon 0df87683e3 fix(est): preserve zero workflow totals 2026-11-07 02:23:26 +09:00
eomsangdon b14c4fccaf feat(est): show existing quantity links 2026-11-06 23:04:58 +09:00
eomsangdon a138efa5f4 fix(est): parse approximate value annotations 2026-11-06 22:59:17 +09:00
eomsangdon ed2d95745e feat(est): save multiple quantity text edits 2026-11-06 22:51:01 +09:00
eomsangdon 0880665f28 feat(est): save print settings to ESTX 2026-11-06 22:45:11 +09:00
eomsangdon c4c3be0a7c feat(est): read project print settings 2026-11-06 22:37:43 +09:00
eomsangdon 8f9251d627 fix(est): parse marine quantity formulas 2026-11-06 22:12:04 +09:00
eomsangdon 064bc6df4c feat(est): export basic JDL unit details 2026-11-06 21:53:46 +09:00
eomsangdon 2a90c78aac feat(est): import basic JDL unit details 2026-11-06 21:48:08 +09:00
eomsangdon d0f96ff1d6 fix(est): retain unchanged JDL content 2026-11-06 21:42:13 +09:00
eomsangdon 8253fb8424 feat(est): save quantity calculation results 2026-11-06 21:32:02 +09:00
eomsangdon fe9f6af161 feat(est): preserve formatted quantity edits 2026-11-06 18:34:04 +09:00
eomsangdon bd702cf948 feat(est): save native options and expand blobs 2026-11-06 17:48:44 +09:00
eomsangdon a5157dd3a9 feat(est): add document and exchange workflows 2026-11-06 15:13:29 +09:00
eomsangdon 74ab66b00e feat(est): complete four business workflows 2026-11-06 05:51:38 +09:00
eomsangdonandClaude Opus 5.5 4e08509700 knowledge(계획): 계획 이관 790~794 — 단가산출 줄 식 시트 · 엔진 의미 · 식 칸 · 경쟁사 글 식 호환 · 근거 제안 · M01 시트 · 흐름도 걷음 · M01 보강 · 판 표지
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:15:32 +09:00
eomsangdonandClaude Sonnet 5.5 43e061ba2a test(791-1): 마스터 지문 해시가 줄바꿈(CRLF/LF) 무관 + 지문 fixture 마스터 칸 재저장(결과 칸 그대로)
폴더마다 체크아웃 줄바꿈이 달라 메인 폴더에서 test_794_2b 셈 지문 시험이 깨짐.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 20:09:38 +09:00
eomsangdonandClaude Opus 5.5 32c200264e chore(794-2a2): dev 받은 뒤 마무리 — 개정 칸 다시 채움 · 지문 1,519 · 낡은 기대 셋
- build_로직_개정.py 다시 돌림 — 새 GF000335 · GF000336 개정 [2026](GX 출처 「자체」 = 빈칸)
- test_791_1 지문 fixture 1,516 → 1,519(기존 줄 같음 · GF000284 = 중간값 칸만 늘고 금액 같음) · test_794_2b 마스터 해시 같은 fixture
- test_713_3 — 로직 수 1,515 → 1,519(794-2b 잔토처리 1 + 794-2a2 셋) · 묶음 335 → 337(794-2b 3-2-1) · 부르는 512 → 513(채집 GX) — 낡은 수
- test_263_2 — GF000284 입력 넷 뒤에 적재기계 · 덤프(794-2a2 고르기 덧) — 낡은 기대

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:05:18 +09:00
eomsangdonandClaude Opus 5.5 373e2121b4 Merge remote-tracking branch 'origin/dev' into sub_laptop_2
- _키대장 「다음」 겹침 — 양쪽 큰 값(GF 337 · GC 1195 · GX 5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:56:57 +09:00
eomsangdon 26d1b41f3f Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_2 2026-11-05 19:56:33 +09:00
eomsangdon 51c864d212 Merge remote-tracking branch 'origin/sub_laptop_3' into sub_laptop_2 2026-11-05 19:56:32 +09:00
eomsangdonandClaude Opus 5.5 254d275d21 feat(794-2a2): M01 보강 — 자재 운반 GF 둘 · 기계 채집 자체 GX · 덤프운반 GF000284 적재기계 · 덤프 고르기
- GF000335 시멘트운반 · GF000336 철근운반(산림 10-1 · 10-2) — 운임 = 자재 구역화물(톤급 · 거리 줄) ÷ 1.1 ÷ 적재 톤 · 하차 품 = 입력(기본값 없음 · 옛 판 값은 설명 참고만)
- GX000004 깬잡석 · 야면석 · 고임돌 · 파쇄석 채집(기계) — 굴착기 1회 N개 · 건설 공통 8-2-3(GC001147) · 상차 · 소운반 덤프 8-2-8(GC001155) · 실무 봉화#3 Q 12.38 · 9.44 재현
- GF000284 입력 끝에 적재기계(굴착기 0.2~1.0 · 로더 타이어 1.34 · 1.72) · 덤프(15 · 8 · 24ton) — 첫째 = 원문 · 기존 셈 그대로(54 조합 전후 같음)
- 빌더 build_로직_M01보강_794_2a2.py = M01 [저장] 길 · 지킴 시험 test_794_2a2_m01_add.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:56:16 +09:00
eomsangdon 5246cfdfed Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-05 19:51:46 +09:00
eomsangdon f5f9ad1075 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-05 19:48:32 +09:00
eomsangdonandClaude Opus 5.5 28ed218d34 test(794-2b): 근거 제안 listing · block 이 같은 폴더 다른 HP 저장본과 대조해 판 차이 강함 표지
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:46:05 +09:00
eomsangdonandClaude Opus 5.5 2101601f39 feat(794-2c): 근거 제안 엔진 후속 — 같은 말 묶음 · 옛 번호 대조표 · 기계 작업량 길 넓힘 · 섞기 오인 넷 고침
- 새 M02_San_Basis_Map — 옛 번호 대조표(건설 옛 11-3 → 8-2-3 · 3-7 → 3-9-1 · 8-2-35 → 8-2-32 · 근거 = 원문 표 값 · 실무 글) · 사방(옛) = 판 대응표 2018 통합 판 · 기계 상수 줄 글 읽기(여러 대입 한 줄 · 괄호 설명 · 끝값 없는 나눗셈) · 작업량당 기계경비 로직 낱말 표
- 엔진 — 같은 말(휠라 = 채움재 · 평떼 = 평잔디 · 육샹 = 육상) · 건설(옛) 같은 번호는 같은 절 + 제목 뜻 겹침만 · Q 식 절 기호 채움(고르는 차례는 채우기 전) · 값 · 제목 길이 품 검산으로 안 받쳐지면 그 기계 작업량당 기계경비(차례 「기계」 · 번호 길은 그대로)
- 검산 probe — 고르기가 범위뿐이면 용량 · 규격 값이 드는 범위 하나로(버킷용량 → 규격구분)
- 459 블록: 없음 85 → 58 · 확정 125 → 164 · 근접 135 → 145 · 사람 114 → 92 · 확정 · 근접 → 없음 0 · 섞기(GF000132) 오인 넷 → GC001148 · GC001189
- 시험 test_793_3a 넓힘 — 없음 ≤ 58 · 확정 ≥ 164 · 손 확인 견본 열 · 1/3 적용 지어 넣지 않음 · 상수 읽기 · 대조표
- 근거후보 꼴 그대로 · M01 · 지식DB 고침 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:45:21 +09:00
eomsangdon 4634a9a871 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1
# Conflicts:
#	M01_MasterData/M01_MasterData_UI_LogicLab_Detail.ts
2026-11-05 19:43:52 +09:00
eomsangdonandClaude Opus 5.5 616dfa698a feat(794-2b): 근거 제안 까닭 「판 차이」에 판 번호 · 둘 이상 파일 같은 배 = 「판 차이 강함」
- M02_San_Basis_Check 새 함수 edition · strong(causes 안 고침 · 셈 · 단 · 꼴 그대로) — 블록 판 = 글 속 「20xx년 품셈」 › 파일 이름 연도 · 기초 로직 줄 「개정」 가운데 그 뒤 것 → 근거후보 덧칸 `판` · 까닭 「판 차이('24 보완 · 파일 2024)」
- 같은 기초 · 같은 배(±2 %)가 같은 폴더 다른 HP 저장본에도 판 표지와 함께 있으면 판 차이 · 현장 값 · 그 밖 → 「판 차이 강함(…)」(읽을 때 · 저장본 안 고침)
- M02_San_Basis_Store — one 끝 edition(HP 파일 이름) · listing · block 끝 strong · 저장본 VERSION 794-2b.1
- 지킴 시험 test_794_2b_edition 7 — 개정 칸 = 빌더 값 · 셈 지문 건너뜀 없음 · 잔토처리 ㎥당 보통인부 0.2 · 봉화#98 철근공 ×0.9714 「판 차이('24 보완 · 파일 2024)」 · 봉화#8 · 거창#28 뒤채움 ×1.98 판 차이 강함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:42:34 +09:00
eomsangdonandClaude Opus 5.5 e1a1db7797 feat(794-2b): 로직 줄 칸 「개정」 자동 채움 + 잔토처리(인력) 단독 GC001194 · M01 기본정보 · 시트 근거 줄에 개정 표시
- 빌더 scripts/build_로직_개정.py — 원문 절 머리 「('08, '20, 25년 보완)」 연도 목록 · 산림 [2026] 제정 · 연도 없는 머리는 윗절 · 글 꼴 그대로 출처 뒤에 끼움 · 채움 1,260 / 1,516(빈칸 256 = 머리에 연도 없음 246 · 못 찾음 10)
- 빌더 scripts/build_로직_잔토처리.py — 건설 공통 3-2-1 비고 ㎥당 보통인부 0.2인 · 호표 QC000172 「잔토처리 보통인부」(GC000083 옵션과 같은 표) · 키 GC001194
- _틀.md 7 장 열 차례 · 칸 설명 · _갱신절차.md 로직 빌더 줄
- M01 셈 지문 1,515 전후 같음 · fixture 다시 뜸(새 줄 하나 더함 1,516)
- M01 화면 읽기만 — 기본정보 「개정」 칸 · 시트 근거 줄 「개정 '08 · '20 · '25」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:42:25 +09:00
eomsangdon d654e055c3 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-05 19:39:25 +09:00
eomsangdonandClaude Opus 5.5 598b80eb26 refactor(794-1b): M01 흐름도 패널 걷음 — 로직 상세 오른쪽 = 원문만 · [신규 로직] = 빈 시트 줄 · 흐름 서버 길 · DataFlow 화면 지움
요구 651 · 653 — 계산 공식 참고용 흐름도(① M01 로직 상세 오른쪽 접는 패널)를 걷음. ② M02 일위대가 다이어그램은 그대로.

지움(64 파일 · 18,666 줄 · 되돌릴 길 = git 이력)
- M01 6 파일 1,064 줄 — Store_Flow.py · UI_LogicLab_Flow.ts/.css · FlowRows.ts · FlowView.ts/.css
- M02_MasterTemplete/DataFlow 43 파일 12,799 줄 — 흐름도 화면 · 셈(Compute · Convert · Lookup · Model · Eval …)
- 시험 15 파일 4,803 줄 — test_m01_logic_flow{view,_engine,_project,_save} · test_m02_df_*(값 찾기 values_old 뺌) — 지운 흐름 코드만 지키던 것

남김(바닥) — DataFlow M02_DF_UnitPick(일위대가 셋) · M02_DF_Values(M02_Item_Prices) · M02_DF_Round · _standard 표 ·
  M02_DF_Router = GET /api/m02/df/standards 하나 · M02_DF_Api = cfStandards 하나 · CfRound 꼴은 Round 안으로

고침
- M01 Router — /save = Store_Make.save(개정 확인 · 자체 파일 새 줄 GX · added 그대로) · /calc /text /logic/auto = 옛 셈만 · /logic/flow 둘 지움
- master_formula — 흐름 훅 지움 · 줄에 「흐름」 이 남으면 「흐름도 로직은 걷음」 까닭 오류(저장된 흐름 로직 0 — 마스터 · storage 다시 확인)
- Sangeun_Calc._defaults = 옛 자동값 · M02_Logic_Lib_Read /logic/flow 지움 · main.py 주석
- 화면 — LogicLab(startFlow · scheduleFlow · 예시값 잇기 지움 · [신규 로직] 줄 = 입력 · 중간 · 호표 빈 목록 + 끝수 null) ·
  Side(원문 패널 하나) · Source(머리 칸 이름 m01lab-src__top/__title · DF_Panel.css 안 씀) · Detail · View(nowInputs 지움) · Sheet(흐름 갈래 지움) · 글
- 시험 — 지운 파일을 가리키던 목록 넷(spreadsheet_size · 208_1 · 588 · input_prefs) · 717_1 흐름 길 두 줄 · values_old 흐름 셈 부분 지움
- test_713_3_list 기대 — 로직 1,461 → 1,515 · 묶음 307 → 335 · 작업량 21 → 65 · 부르는 506 → 512(782-1 · 2 · 3 건설 기초 로직 54 더함 ·
  옛 키만 세면 307 · 21 · 506 그대로)
- 새 지킴 test_794_1b_flow_gone — 흐름 길 404 · 표준 표 남음 · 신규 로직 자체 GX · 흐름 남은 줄 까닭 오류

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:38:39 +09:00
eomsangdonandClaude Opus 5.5 d7d1b695f3 knowledge(요구): 요구 653 — M01 보강 여덟 · 다이어그램 ① 만 · 경쟁사 원본 사본 지움(제안대로)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:14:28 +09:00
eomsangdonandClaude Opus 5.5 9456935d2d feat(793-3b): 시트 상태줄 원래 금액 글 — 가져온 식이면 「엑셀 원래」(브레인 승인 · sub3 시트 파일 두 줄)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:51:36 +09:00
eomsangdon 141c45061a Merge remote-tracking branch 'origin/dev' into sub_laptop_2 2026-11-05 18:49:35 +09:00
eomsangdonandClaude Opus 5.5 fb51c09832 style(793-3b): 근거 제안 단마다 한 줄(단 배지 · 후보)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:48:42 +09:00
eomsangdon 6633784fc1 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-05 18:46:47 +09:00
eomsangdonandClaude Opus 5.5 2e7328d3a5 feat(794-1): M01 관리자 줄 식 시트 — 로직 상세 첫 보기 「시트」 · 식 칸 칠 때마다 검사 · 기존 [저장] 길
- 로직 상세 보기 첫 칸 「시트」(기본) — 이름 | 식 | 값 | 비목 · 근거 · 옵션(시험 값) · 변수 · 계산 · 자원 · 율 · 덧줄 · 단계 · 끝수 · 소계 · 합계 · 상태줄 지금 · 고치기 전
- 식 글 = 이름 꼴(M02 단가산출 시트와 같은 번역 · 새 서버 M01_MasterData_Sheet.py · POST /api/m01/logic/sheet · /logic/sheet/check · 저장 때와 같은 엔진 검사 · 쓰기 없음)
- 식 고침 = 시스템 관리자만 · 되는 식만 줄 사본에 · 캐시 → 기존 [저장](개정 확인 · 서버 검사 422) · [원래로]
- 식 칸 몸 = 공용 A00_Common/sheet_formula_cell.ts — M02 단가산출 시트도 같은 것을 씀(동작 그대로)
- 자체 로직(GX) 편집기 식 날 입력칸 → 읽기만(시트 식 칸으로 갈음)
- 시험: test_794_1_m01_sheet 새로 · test_790_1 식 칸 문구 자리를 공용 파일로 옮김(식 칸 몸이 공용으로 감) · test_717_2b · test_722 LogicLab.ts 줄 상한 627 → 637(시트 이음 10 줄 · 셈 · 고침은 시트 파일로 뺌 · 700 아래)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:45:48 +09:00
eomsangdon f9bd0d71bb Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_2' into main_laptop_1 2026-11-05 18:44:42 +09:00
eomsangdonandClaude Opus 5.5 80c3f72a24 perf(792-2): 시트 다시 셈 서버 다듬기 — 자재 색인 · 찾기 답을 M01 색인에 한 번 · 합판 1.2~1.6 s → 62 ms
- `_Over` 덮개가 자재 색인(`_market` · `_by_class`) · 찾기 답(`_found`)을 속 M01 색인에 둠 — 셈마다 3만 줄 다시 안 지음 · M01 파일 자국이 바뀌면 새 색인 = 새로
- `master_pick.search` = 마스터 · 조건 · 지역 · 단위마다 한 번(`_search` 가 옛 몸) · 사본을 줌
- `calc_doc` = M01 `steady()` 안 — 한 셈 동안 파일 자국 한 번(37 번 → 1)
- 측정(8001 · 10 번 중앙값 · 옵션/상수/식/변수 줄) 합판 62/62/62/66 · 지게 30 · 굴착기 31~36 · 봉화 27 ms · 금액 전후 같음
- 엔진 일곱 번은 남김 — 한 번 ~5 ms · 목표 아래
- 시험 test_792_2_calc_speed(색인 다시 안 지음 · 자국 바뀌면 다시 · 층 섞임 없음 · 셈 답 같음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:44:03 +09:00
eomsangdonandClaude Opus 5.5 9b92d67189 feat(793-3b): 근거 제안 세 단을 793-3a 길로 이음 · 이 줄에 잇기 고른 것 = 옮길 층 SK · 환산 계수 넣을 때 서버가 다시 바꿈
- 가져온 식 만들 때 SK 가져온.근거후보 = 793-3a 블록 제안 근거후보(San_Basis_Store.block · 못 셈 = 빈 칸)
- 머리 판 후보 = 원문번호 · 옵션 · 품검산 · 입력필요 n · 까닭 [표지 글] · [우리 기초로 바꾸기] 입력 필요 = 바인딩 + 후보 입력필요 + 환산
- 환산 계수 = 가져온 식에서 to-base 다시(바꿈 전 · 후 서버 정본) · 계수 없을 땐 시트 안 엶(셈 길 400 없음)
- 단산 줄 고른 것 층 · 주인 뺌 — 층을 주면 옮기기가 SK 를 못 찾아 고정 줄로 옮겨짐
- 시험 test_793_3b 9(근거후보 채움 · SK 참조 옮기기)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:41:43 +09:00
eomsangdonandClaude Opus 5.5 ab36bcd18c knowledge(요구): 분석 794-2a — M01 보강 후보 · 품셈 판 차이 · 판 표지 꼴 · 사용자 고를 것 여덟
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:38:29 +09:00
eomsangdon 538151177e Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_2 2026-11-05 18:26:15 +09:00
eomsangdon a66de39845 Merge remote-tracking branch 'origin/dev' into sub_laptop_2 2026-11-05 18:26:13 +09:00
eomsangdonandClaude Opus 5.5 5a6cccd997 fix(793-3b): 우리 기초로 바꾸기 — 옵션 = 블록 풀이 후보 바인딩 · 단위 성격 다르면 환산 계수 입력 필요 · 가져온 식 시트 M01 바뀜 표지 걷음
- to-base 몸 환산? · 환산 없이 단위 성격 다름 = 후 None · 필요 ["환산"](막지 않음 · 화면 붉은 칸)
- 화면 bind = 블록 풀이 그 기초 후보 바인딩(엑셀 값 · 기본) + 블록 검산 입력 필요 · 줄 검산(빈 입력값)은 옵션을 못 읽었음
- 가져온 식 SK = M01 짝 없음 — 시트 M01바뀜 · 짝잃음 안 띄움(저장 M01판이 바탕 줄과 달라 늘 떴음)
- 시험 test_793_3b 8

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:26:07 +09:00
eomsangdonandClaude Opus 5.5 d8834c1d63 feat(792-4): B09 일위대가 대가 블록이 엔진 새 의미를 따름 — 다섯 길 같은 금액 · 가져온 식 SK 내역 = 엑셀
- 새 B09_Estimation_Labor_Rule: 새 의미 로직만 대가 블록 줄 `place`(줄 자리) · `out`(합밖 · 제외 단계) · 덧줄 = 수 하나 × 밑 합 꼴이면 `rows:` % 줄(줄.'이름' · 앞 덧줄 · 번지 · 단계 · 비목 합 · 793-2a 괄호 꼴) · 이름 없는 수 식 덧줄 = 고정 줄 · 블록 `rule` {끝전, 조정, 끝수} · 다른 꼴 = 빈 줄 그대로(틀린 값 대신 빈칸)
- Labor_Source: 품셈 블록에 표지 · 단가산출 블록이 줄을 바꿔도 표지 이어 받음(657 줄)
- Calc_Labor .py · .ts 짝: 줄 금액 `cut`(place) · `out` 줄은 비목 합 · part: 밑에서 뺌 · 블록 비목 합 `settle`(큰나머지 → 끝수 → 조정 → 원 버림 · master_cut.settle 차례) · 옛 블록 = 표지 없음 = 전과 같음
- 산근 글 로직 부르기 줄(중기 세 줄 · 하위 산근) 금액도 줄 자리 규칙(792-3 남은 것)
- test_792_3: 다섯째 길 B09 대가(SC → 굳힌 SK 수량 1 → 내역 단가 · 파이썬 = TS 거울) · 실무 봉화 D00102 · 북부청임도 18 을 793-2 길(임시 층)로 가져온 식 SK → 내역 단가 = 엑셀
- test_712_3 낡은 기대: 표 비율 0.78 → 단위 수 1,601 이상 · 0.76 — 782-1 · 782-2 · 782-3 계수 표 94 가 로직에 붙음(그중 단위 기준 25 · 시공능력 · 적용기준 계수 = 단위 기준 없음) · 712-3 때 2,023 중 1,601 그대로 · 마스터 자료 안 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:26:03 +09:00
eomsangdon 3ac9aff2f0 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1
# Conflicts:
#	M02_MasterTemplete/M02_Hopyo_Router.py
2026-11-05 18:21:35 +09:00
eomsangdon be28927a65 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-05 18:20:30 +09:00
eomsangdonandClaude Opus 5.5 532cc52c8e feat(793-3a): 근거 제안 엔진 — 번호 ∩ 값 ∩ 제목 ∩ 기계 상수 → M01 기초 + 옵션 · 품 검산 · 세 단
- M01 값 역색인 · 숫자 정규화 한 벌(N1~N10 · 단위 기준 · % ↔ 소수 · t ↔ kg · 인/양 몫) · M01 지문마다 한 번 · 서버 시작 때 뒤에서 지음
- 값 길(상수 지문 → 로직 · 우연 보정 유의 4) · 합친 길(분석 648-D2 차례 · 후보 2~6 엔진 품 대조) · 검산 · 옵션(B1~B6 → 값 길 V → 고르기 맞댐 → 입력 필요)
- 세 단 확정 · 근접(판 차이 · 현장 값 · 감률 · 할증 배수 · 단위 기준 차이) · 사람(+ 값 약한 1 등) · 없음(M01 보강 목록)
- 길 GET hopyo-basis/{키} · hopyo-basis/{키}/{번} · 파생 저장본 · 올림 때 HP 숨은식(STmate G 식) · 끝내기 때 지움 · 판정 길 그대로
- 보정 자료 = 여덟 파일 상수 1,094 · 시험 test_793_3a 11(459 블록 기초 260 · 옵션 전부 52 · 품 같음 141 · 없음 85)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:19:26 +09:00
eomsangdonandClaude Opus 5.5 ca72718e26 feat(793-3b): 불러오기 단산 줄 = 가져온 식 단가산출 모달 — 블록 → 가져온 식 SK 길 · 검산 · 엑셀/우리 단가 · 이음 고르기 · 근거 세 단 · 우리 기초로 바꾸기 · 입력 필요 · 줄 잇기 · 결과 칩
- 서버: 올림 때 산근 시트 둠(실무자료/산근/<HP>.json · HP 지울 때 같이) · POST …/sangeun/from-hopyo(블록 → 793-2a 내보내기 → 793-2 만들기 · 같은 내용 = 그 SK 다시 씀 · 옛 HP 409) · STmate 끝전 = 엑셀 계와 맞는 쪽
- 화면: [수정] 모달 단산 줄 · 오른쪽 블록 판 [가져온 식으로 열기](새) → 시트 모달 + 머리 판 · [이 줄에 잇기] = 줄 고른 것 SK(입력 필요 남으면 확인 막힘) · 단산 줄 결과 칩
- 시험: test_793_3b_import_screen 6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:09:00 +09:00
eomsangdon 71c02e6704 Merge remote-tracking branches 'origin/CODEX' and 'origin/sub_laptop_1' into main_laptop_1 2026-11-05 18:05:11 +09:00
eomsangdon 8ad1853242 fix(est): preserve fees in independent annual copies 2026-11-05 18:04:56 +09:00
eomsangdonandClaude Opus 5.5 a0bcf5b48a feat(792-3): run_line · B09 산근 셈이 엔진 새 의미를 따름 — 네 길 같은 금액
- master_cut: 엔진 답 덧칸 `셈법`(새 칸 뗀 작은 로직) · `plain(got, gone)` 이 셈법 있으면 줄 자리 · 합밖 · 단계 · 블록 끝전 · 조정 그대로(켜쌓기율 · 별도계상 뒤 다시 버림이 큰나머지 · 조정을 덮던 흠) · `kept`
- M02_Labor_Doc: run_line 답이 단계 · 셈법을 넘김 · drop_apart 가 plain 에 뺄 줄 차례를 줌(686 줄 · 늘지 않음)
- 새 B09_Estimation_Sangeun_Sum: 산근 풀이 · 산근 글이 새 의미 로직만 엔진 규칙(표시 · 줄 자리 · 합밖 · 번지 · 단계 · 끝전 · 조정) · 옛 로직 = Q ROUND 2 · ROUNDDOWN 1 · 0 그대로
- B09 산근 글: 로직 줄(중기 세 줄) 금액을 줄 하나로 묶어 차례를 호표와 맞춤(합 같음)
- 지킴 시험 test_792_3_engine_paths: 견본 넷(봉화 · 소광 · 영월 · 임도) + 단위 로직 = 엔진 · M02 SK · SC 한 줄 · B09 산근(글 계 포함) 같은 금액 · 다시 버림 셈법 지킴
- test_791_1: 견본 넷을 만들기 함수로 떼어 냄(같은 기대)
- test_222_1 낡은 기대 셋 고침: 751-3 끝수 뒤 별도계상 뺀 기준 = 남은 줄 비목 합 원 버림(소수 없음) · 옛 기대는 「원 버린 기준 − 소수 재료 금액」이라 0.6 원 어긋남
- test_145_1: 작업량 Q 이름 규칙이 B09_Estimation_Sangeun_Sum 으로 옮김

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:04:11 +09:00
eomsangdon f255ebc0fe fix(est): preserve precision and parse source notes 2026-11-05 17:57:49 +09:00
eomsangdonandClaude Opus 5.5 2a2822d650 feat(793-2): 가져온 식 793-2a 내보내기 꼴 받음 — 자원 덧칸 · 글줄 이름 → cid · 자리들 합 · 단계 · 중간 검산 · 빈 산근 받음
실무 여덟 파일 455 블록 검산 같음 · 상수 자리 찾기 「(11)」 겹침 고침(M01 식엔 그 꼴 없음 = 옛 상수덮개 차례 그대로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:51:33 +09:00
eomsangdon e3262d2b49 Merge remote-tracking branch 'origin/dev' into sub_laptop_2 2026-11-05 17:44:39 +09:00
eomsangdonandClaude Opus 5.5 e393d67d98 feat(793-2): 가져온 식 단가산출 SK 담기 — 바탕 갈래 · 원본 메타 · 검산 · 우리 단가로 · 이음 고름 · 우리 기초로 바꾸기 · 만들기 길
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:44:31 +09:00
eomsangdon badbd33708 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-05 17:44:11 +09:00
eomsangdonandClaude Opus 5.5 b8cad4f92e feat(793-2a): 내보내기 꼴을 793-2 가져온 식 SK 만들기 몸에 맞춤 — {로직, 원본}
- 호표 줄 = 비목 하나 인력 · 재료 · 기계 → 종류 · 비목 · 단가(엑셀) · 이음(M01 키) / 그 밖 = 로직 값묶음
- 원본 = 파일 · 시트 · 행 · 갈래 · 엑셀금액 · 글줄 {행, 글, 자리, 값} · 표지 · 계약 · 로직 키 · 요소 뺌
- STmate 비목 꼬리 없는 참조도 값 든 비목 하나면 단가 줄
- test_793_2 — 원본 글줄 줄마다 엔진 = 엑셀(금액 줄 1,315 · 계표시 695 · EST 2,903) · 단가 줄은 서버처럼 값을 얹어 셈

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:43:20 +09:00
eomsangdon b989fe1724 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-05 17:41:55 +09:00
eomsangdonandClaude Opus 5.5 09a67ce91f feat(792): 단가산출 시트 식 칸 화면 — 줄 식 고침 · 칠 때마다 검사 · 빨간 칸 까닭 · 변수 줄
- 새 M02_MasterTemplete_Sangeun_SheetFormula.ts — 줄마다 [식 고침] → 이름 꼴 식 칸 · 칠 때마다 formula-check(옛 요청 끊기) · 틀리면 빨간 칸 + 까닭 · 바꿀 때 엔진 식을 식덮개로(같은 자리 상수 · 표값 · 품 덮개 걷음) · 빈 칸 · M01 식 = 풂 · 더한줄 수량식(율 · 로직 줄 뺌)
- 변수 줄 — [+ 변수 줄] · 이름 · 식 · 값 · 빼기(Formula_Model addVar · editVar · dropVar) · 자리 뒤에 그림
- Sheet — 식 덮개 줄은 품 칸 안 둠 · 숫자 칸 대신 식 칸 · [전체 M01 원래로]에 식 덮개 셈
- Detail — 시트 옵션에 층(scope)만(줄 수 그대로)
- test_790_1 — 식 칸 정적 단언

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:40:07 +09:00
eomsangdon f033abdf6e Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_4 2026-11-05 17:37:35 +09:00
eomsangdon 03d2fe765f Merge remote-tracking branch 'origin/dev' into sub_laptop_4 2026-11-05 17:37:34 +09:00
eomsangdonandClaude Opus 5.5 193c3992a9 feat(793-2a): 경쟁사 산근 식 → 엔진 로직 줄 꼴 내보내기 · 여덟 파일 엔진 금액 = 엑셀
- 새 M02_San_Emit.py — 793-1 줄 나무 → 로직 JSON(중간 · 호표 값묶음 · 덧줄 · 번지 · 단계 · 합밖 · 자리 · 끝전 · 조정 · 글줄 · 가져옴 검산 · 계약)
- 자원 이음 열쇠(품명 · 규격 · 단위 · 경쟁사 코드 · 1차 줄 판정 M01 키) · reprice 단가 전파
- STmate 누계 % 밑 = 그 줄 앞까지(뒤 호표 빼고 · 안 빠진 제외 단계 더함) · 율 범위 C = 앞 줄 부름 합
- 지킴 시험 test_793_2 — STmate 233 · EST 222 블록 엔진 셈 = 엑셀 = 참조 평가기 · 줄 4,218 · 단계 695 · 전파 견본 7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:37:26 +09:00
eomsangdonandClaude Opus 5.5 c0604b917f feat(793-2): 가져온 식 단가산출 이음 꼴 — 로직 줄 · 원본 · SK 덧칸 가져온 · 길
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:36:34 +09:00
eomsangdon f4bdd30126 Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-11-05 17:34:10 +09:00
eomsangdonandClaude Opus 5.5 93b13e975f feat(790-3): 단가산출 시트 숫자 넓힘 화면 — 식 속 상수 · 표값 칸 · 글 줄 · 까닭 · M01 원래로 · M01 바뀜
- 새 M02_MasterTemplete_Sangeun_SheetEdit.ts — 790-2 셈 답 시트.자리 를 칸으로: 식 속 숫자 = 상수 덮개(식 모양 그대로 · 옵션 따라감) · 표 꼬리표 값 = 표값 덮개(「표 연결 끊김」) · 글 줄 더함 · 고침 · 지움(줄마다 [+ 글] · 근거 머리 [+ 근거 글]) · 까닭 칸(고친 줄 · 뺀 줄 · 더한 줄) · [M01 원래로] 줄마다 · 상태줄 전체 · 「M01 바뀜」 배지 · 짝잃음 띠
- Sheet_Model — 식 조각(서버 scan 숫자 · 표 찾기 차례 · 안 맞으면 칸 안 그림) · 산출서에 글 줄 · 되살린 더한줄 열쇠 밂(liftAddKeys)
- Sheet — 옛 [되돌림] 단추를 [M01 원래로]로 · 더한줄 빼면 까닭 · 글줄 열쇠 당김(dropAddKeys) · 문서 고침 = 캐시 → 서버 셈 · 자동저장 없음
- 더한줄 줄 사이 끼움은 SK 모델에 자리 칸이 없어 덩이 끝 그대로(글 줄만 자리 뒤)
- test_790_1 늘림(정적 단언 + 식 조각 · 글줄 산출서 · 열쇠 밂)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:34:02 +09:00
eomsangdonandClaude Opus 5.5 bb39bacd26 feat(792-1): 단가산출 식 칸 TS 캐시 모델 · 지킴 시험 · 같은 내용 지문에 식 칸
- 새 logic/M02_Sangeun_Formula_Model.ts — setFormula · addVar · editVar · dropVar(서버 clean 이 받는 꼴)
- Const_Model restoreM01 · coverKinds 에 식덮개(서버 restore · kinds 거울)
- M02_Library_Same SK 지문에 상수 · 표값 · 식 덮개 · 변수줄(식만 다른 SK 를 같은 것 409 로 막던 흠 · 빈 칸 = 옛 지문 그대로)
- Formula 검사 길 0 나눔 알림이 못 읽는 식에서 멈추던 흠
- 새 test_792_1_sangeun_formula.py 13 — 할증율 변수 줄 51,501 · 합판거푸집 공구손료 식 51,802 · 수량식 · 이름 꼴 왕복 · 검사 까닭 · 상한 · 순환 · 옛 SK · 저장 400 · B09 굳힘 · TS

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:29:40 +09:00
eomsangdon 61ebb81971 Merge remote-tracking branch 'origin/dev' into sub_laptop_2 2026-11-05 17:23:17 +09:00
eomsangdonandClaude Opus 5.5 710827e901 feat(792-1): 단가산출 사용자 식 칸 서버 — 변수줄 · 식덮개 · 더한줄 수량식 · 이름 꼴 ↔ 엔진 식 · 검사 길
- 새 M02_Sangeun_Formula.py — settle(바탕 줄 + 식 칸 → 엔진 줄 · 틀린 자리 = M01 식 + 짝잃음 · 저장 = 400) · to_name · to_engine(× ÷ · [노임 …] · [별칭]) · check_doc · 상한(길이 300 · 괄호 12 · 변수 100) · 셈 답 덧칸(식글 · 원식글 · 식덮개 · 변수 줄 · 이름표)
- Const formulas 첫머리 · clean_more 끝 · stamp(저장 엔진 식) · restore · kinds · no_flat · covers_n 에 식덮개
- Doc patched · _add_item 두 줄 고침(줄 수 그대로) · Calc stamp 한 줄
- 길 POST /api/m02/layers/{층}/sangeun/formula-check
- M01 로직 1,515 식 9,228 이름 꼴 왕복 같음 · 기존 SK 시험 99 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:23:05 +09:00
eomsangdonandClaude Opus 5.5 d26f7a7e1a fix(790-1): 시트 화면 검증 흠 고침 — 글 옵션 칸 · 품 소수 여섯 자리 · 저장 뒤 고치기 전 · 근거 값 칸
- 고르기 없는 옵션(자재지역 「포항」) = 글 칸(숫자로 읽어 NaN 뜨던 흠) · 단위 겹침 뺌
- 품 · 단가 칸 보임 = 소수 여섯 자리(0.1111111111111111 → 0.111111)
- [저장] 뒤 상태줄 「고치기 전」 바로 새로 그림
- 근거 줄 값 칸 「—」 뺌

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:17:36 +09:00
eomsangdon c20a18bf7a Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_3 2026-11-05 17:05:59 +09:00
eomsangdon 29b8205df8 Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-11-05 17:05:57 +09:00
eomsangdonandClaude Opus 5.5 48d3d4e1af feat(790-1): 단가산출 줄 식 시트 — 상세 첫 탭 「시트」 · 이름 | 식 | 값 | 비목 · 산출서 보기 · 상태줄 금액 셋
- 새 M02_MasterTemplete_Sangeun_Sheet.ts(+ .css) — 보기 고르기(시트 + 옛 넷 그대로) · 줄 갈래(근거 · 옵션 · 변수 · 계산 · 자원 · 율 · 소계 · 합계) · 칸에서 옵션 · 품 · 단가 · 율 % · 빼기 · 되살림 · 되돌림 · [+ 자원 줄] · [+ 율 줄] · 고칠 때마다 서버 셈(최신 응답만) · M01 과 다른 줄 색 띠
- 새 logic/M02_Sangeun_Sheet_Model.ts — 시트 줄 · 읽기 쉬운 식(텍스트 수식 이름글 · 별칭) · 엔진 날식 숨김 · 산출서 한 줄 글 · 상태줄(M01 원래 · 지금 · 고치기 전)
- ui_template_calc_row 시트 줄(createCalcSheetRow · is-changed) + 부품 모음 견본 한 줄
- Sangeun_Detail = 탭 등록만(createViewPicker → createSangeunSheet)
- 시험 test_790_1 새 · test_721 단추 자리 단언을 새 보기 고르기 이름으로 고침(createViewPicker 걷음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:05:43 +09:00
eomsangdon 09cdf42ebb Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-05 17:04:25 +09:00
eomsangdonandClaude Opus 5.5 19ac55f9c6 feat(790-2): 단가산출 숫자 넓힘 서버 — 상수 · 표값 덮개 · 글줄 · 까닭 · M01 원래로 · M01판
- 새 M02_Sangeun_Const.py — 식 n 번째 숫자 · 표 찾기 덮개(엔진 파서 차례 · 식 구조 그대로라 옵션 따라감) · 글줄 · 까닭 꼴 검사 · 저장 때 M01판(자리마다 지문) · 셈 답 덧칸 원래 {노 · 재 · 경 · 계} · 시트 {자리 · 글줄 · 짝잃음}
- Doc.patched 첫머리 · clean 끝 · Calc.clean_doc · calc_doc 한 줄씩 끼움(줄 수 그대로) — 화면 · 저장 원값 · B09 · 펼침 겹침 한 벌
- Library_Flat — 상수 · 표값 덮개 SK 는 안 펼침(참조 그대로 · 값 그대로) · [초기화]가 새 덮개 · 까닭도 걷음
- 새 logic/M02_Sangeun_Const_Model.ts — 캐시 고치기(setConst · setTable · 글줄 · setReason · restoreM01 · dropAddKeys · coverKinds) · 파이썬 짝 거울 시험
- 새 test_790_2_sangeun_const.py — 견본 금액 = _분석_650-H2(61,088 · 51,802 · 38,696 · 35,764) · 옛 SK 꼴 · 금액 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:03:46 +09:00
eomsangdonandClaude Opus 5.5 c05139a971 feat(791-1): 엔진 셈 의미 넓힘 — 금액번지 · 소계 단계 · 제외 · 줄 자리 · 블록 끝전 · 결과 조정 · 변수 표시
- 새 모듈 master_formula_sum.py — 덧줄 고리(master_formula.run 에서 옮김) · 번지 · 단계 · 합밖 · 조정 · 표시 · 검사
- master_cut.py — 줄 자리 {방법, 자리} · 블록 끝전(비목버림 | 큰나머지) · 결과 조정 · 답 단계 목록
- master_formula_check.py — 새 칸 검사(없는 번지 · 단계 순서 · 자리 범위 · 끝전 · 조정)
- _틀.md 7장 「셈 의미 넓힘」 · 8장 덧줄 이름
- 지킴 시험 test_791_1 — M01 로직 1,515 지문 전후 같음 · 새 의미 단위 · 실무 견본 넷(봉화 D00102 · 울진소광 D01489 · 동부청영월 D00003 · 북부청임도 18) = 엑셀
- master_formula.py 690 → 669 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 17:03:36 +09:00
eomsangdon 66e0728d91 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-05 17:00:25 +09:00
eomsangdonandClaude Opus 5.5 428d35cbd6 feat(793-1): 경쟁사 산근 글 식 파서 · 참조 평가기 · 실무 여덟 파일 지킴 시험
- M02_San_Expr — eval 없는 식 나무 · 셈 · 줄 자리 규칙(버림 · 반올림 · 올림 × 0~3)
- M02_San_Stmate — 숨은 G 열 원문 → 줄 나무(계 5 단 · (-=) · 대입 · [A] · ;; · &L/M/S/X/D& · &P/R& 율 · :=A01 · -> · // 덧글)
- M02_San_Est — 보이는 글 → 식 복원(단가 · 앞줄 · 상수 역할 · 값 줄 숨은 합 · 변수 얼림 · 단가 정체 · 모호 표지)
- M02_San_Eval — STmate 셈(특수 % · 끝전 두 갈래 · 결과금액 조정 · 계약단가) · EST 다시 셈 · 줄마다 검산 · 단가 전파
- M02_San_Book — 같은 책 블록 · 단가표 읽기(읽기만)
- test_793_1 — STmate 233/233 · 금액 줄 1,315 · 계 줄 695 · EST 222/222 · 줄 2,901/2,903 · 모호 9 · 전파 견본 7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:59:56 +09:00
eomsangdonandClaude Opus 5.5 bd34ba7bda knowledge(요구): 요구서 652 — 단가산출 줄 식 시트 · 경쟁사 글 식 호환(사용자 확정 · 정함 · 물결 1 일감)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:48:04 +09:00
eomsangdonandClaude Opus 5.5 3f071c9726 knowledge(요구): 요구 652 — 단가산출 줄 식 시트 · 경쟁사 글 식 호환 권장대로 반영
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:47:27 +09:00
eomsangdonandClaude Opus 5.5 17b6ee5460 knowledge(계획): 계획 이관 789 — 단가산출 엑셀처럼 식 편집 검토
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:33:53 +09:00
eomsangdonandClaude Opus 5.5 a4c28aab09 knowledge(요구): 분석 650-H1 · H3 — 경쟁사 산근 편집 흐름 · 단가산출 엑셀처럼 줄 식 시트 제안(E1~E7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:33:52 +09:00
eomsangdonandClaude Opus 5.5 bae8ca4c9c knowledge(요구): 분석 650-H2 — 우리 단가산출 엑셀처럼 편집 가능성 · 다이어그램 진단 · 견본 셋
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:30:48 +09:00
eomsangdonandClaude Opus 5.5 c7a8d706ec knowledge(요구): 요구 651 — 다이어그램(흐름도) 방식 못 씀 · 650 덧말
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:19:51 +09:00
eomsangdonandClaude Opus 5.5 5ce6249a95 knowledge(요구): 요구 650 — 649 권장 채택 · 단가산출 엑셀처럼 식 편집 검토
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:18:43 +09:00
eomsangdonandClaude Opus 5.5 ab6cb89641 knowledge(계획): 계획 이관 788 — 엑셀 불러오기 개발 방향 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:49:09 +09:00
eomsangdonandClaude Opus 5.5 4e91631464 knowledge(요구): 분석 649-G2 · G3 — 경쟁사 산근 글 그대로 다시 셈 100 % 재현 실측 · 개발 방향(갈래 C 섞음 권함 · M01 고칠 것 M1~M7 · 단계 P1~P5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:49:08 +09:00
eomsangdonandClaude Opus 5.5 22b56940b0 knowledge(요구): 분석 649-G1 — 우리 모델 ↔ 경쟁사 모델 차이 · 경쟁사 불러오기 선례 · 규정 제약
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:43:23 +09:00
eomsangdonandClaude Opus 5.5 48812e3996 knowledge(요구): 요구 649 — 경쟁사 방식 호환 · 단가산출 기초 고침 · 개발 방향 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:22:59 +09:00
eomsangdon 25827d4039 Merge remote-tracking branch 'origin/CODEX' into main_laptop_1 2026-11-05 15:22:09 +09:00
eomsangdon 8001d63852 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-05 15:22:08 +09:00
eomsangdonandClaude Opus 5.5 737c2f678c knowledge(계획): 계획 이관 787 — 엑셀 불러오기 근본 원인 분석
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:46:45 +09:00
eomsangdonandClaude Opus 5.5 904815a1e0 knowledge(요구): 분석 648-D2 · 648-F — 합친 길 실측 · 엑셀 불러오기 근본 원인 일곱 · 해결 방향 일곱
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:46:45 +09:00
eomsangdonandClaude Opus 5.5 c725694d58 knowledge(요구): 분석 648-D — 수 지문 → M01 표 역조회 전수 실측(블록 459 · 절 964 · U/P/A/N)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:27:33 +09:00
eomsangdon 3e9fb28714 fix(est): propagate assigned prices into final totals 2026-11-05 14:23:21 +09:00
eomsangdon a6fc896655 fix(est): parse units in original calculation notes 2026-11-05 14:13:43 +09:00
eomsangdon 114a353b9e fix(est): retain undo history for large projects 2026-11-05 14:08:10 +09:00
eomsangdon b15f7cbf09 fix(est): keep large contract snapshots saveable 2026-11-05 14:05:41 +09:00
eomsangdon 9f9151f56d fix(est): preserve active edits during queued saves 2026-11-05 13:53:12 +09:00
eomsangdon 9a4070fae5 fix(est): compare imported original contract values 2026-11-05 13:46:42 +09:00
eomsangdon c6118c97ed fix(est): preserve component rates in resource totals 2026-11-05 13:39:04 +09:00
eomsangdonandClaude Opus 5.5 8a7991cf33 knowledge(요구): 분석 648-E — EST 원본 54 건 ↔ 실무 엑셀 출처 대조 · BookData · 라이브러리 → M01 대응 어림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:38:29 +09:00
eomsangdon a007d6358a fix(est): allocate annual component amounts 2026-11-05 13:22:24 +09:00
eomsangdon 216e2ae2a7 fix(est): propagate text subtotals and bid rates 2026-11-05 13:17:44 +09:00
eomsangdon f3821043d4 Merge remote-tracking branch 'origin/CODEX' into sub_desktop_1 2026-11-05 13:14:23 +09:00
eomsangdonandClaude Opus 5.5 429a4ddf4b knowledge(요구): 분석 648-C — 실무 엑셀 여덟 파일 단산 블록 459 틀 · 규칙성 · M01 대응 · 근본 원인 신호
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:13:37 +09:00
eomsangdonandClaude Opus 5.5 6937882375 knowledge(요구): 분석 648-A · 648-B — EST · STmate 산근 생성 방식(라이브러리 · 복사 계보 · 식 문법 · 정체 열쇠 · 데스크탑 REA 빈칸)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:08:18 +09:00
eomsangdon 1454b3c315 fix(est): print complete tables beyond page limits 2026-11-05 12:58:19 +09:00
eomsangdon 516d2ae329 fix(est): preserve links and formula precision 2026-11-05 12:51:49 +09:00
eomsangdonandClaude Opus 5.5 bea9bf49c7 knowledge(요구): 요구 648 — 근본 원인 분석 시작 · 단계적 정밀 · REA 는 데스크탑
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:45:00 +09:00
eomsangdonandClaude Opus 5.5 f0fcf7497d knowledge(요구): 요구 647 — 646 답(입력 필요 · 모달) · 기초 없음 뜻 물음 · 근본 원인 분석 먼저
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:07:45 +09:00
eomsangdonandClaude Opus 5.5 bc5fe1c674 knowledge(요구): 요구 646 — 단가산출 기초 줄 옵션 지정 화면 검토(대화만)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 09:49:59 +09:00
eomsangdonandClaude Opus 5.5 bc67ddef8e knowledge(계획): 계획 이관 786 — 불러오기 [수정] 모달 되살림 · 별도계상 품목 고르기 없앰
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 07:47:10 +09:00
eomsangdonandClaude Opus 5.5 56b73b79b8 fix(786-2): 화면 검증 흠 고침 — [구조물 조합으로] 참조 주인 · 실패 되돌림 · 표 머리 칸 · 이름 칸 덧글 줄
- ToItem: SC 참조 주인 = 실제 주인 id(개인 「회사/사용자」 · Find_Pick.upper 와 같은 ownOwner 길) — null 이면 서버 400
  「참조 층 · 주인 꼴이 틀림」 · 일위대가 못 담으면 방금 더한 항목 지움(빈 항목 안 남김)
- CheckRows: [확인] · 행 머리 칸 = 표마다 새 노드(두 표가 같은 노드를 써 로직 표 머리가 두 칸 밀림)
- Hopyo.css: 이름 칸 덧글(셈 못 함 · 입력 필요) 한 줄씩

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 07:41:33 +09:00
eomsangdonandClaude Opus 5.5 b6a18d68e0 fix(786-2): 별도계상 줄 표지 = 786-1 apartPick(서버 제안 고정 줄 하나 · 별도계상) — 고름 칩 없이 「별도계상 · 품목은 프로젝트에서」
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 07:29:05 +09:00
eomsangdon 16661de18e Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-11-05 07:26:46 +09:00
eomsangdonandClaude Opus 5.5 e91fd4398e feat(786-2): 불러오기 [수정] → 모달 「확인 — 제N호표」 옛 꼴 되살림 · 새 기능 「새」 배지
- 새 Hopyo_Check(모달 껍데기 · 머리 = 호표 · 엑셀 단가 · 제안 n / 줄 m · 확인 n · 만든 일위대가 ·
  [호표 제안 모두 확인](새) · [일위대가 생성] = 옮기기 길 이 호표만 · [구조물 조합으로] · [엑셀 보기])
- 새 Hopyo_CheckRows(로직 표 + 자재 표 = 구조물 조합 상세 꼴 · Labor_Rows moneyCell · PRICE_SLOTS ·
  Labor_MatMarks 빌려 씀 · 칸마다 엑셀 / 우리 단가 · 품 비교 · [고르기] · [원문 보기] · [옵션] · 추가 / 로직없음)
- 새 Hopyo_ToItem(구조물 조합 나무 add-item → 그 일위대가 문서에 SC 참조 한 줄 · 옮겨진 뒤만 켬)
- Hopyo_Rows: 시트 아래 줄 판 몸 걷음 → 줄 칸 부품 createRowKit(판정 · 고름 칩 · 블록 풀이 · 단위 환산 · [확인]) ·
  옮기기 한 번 runMove(오른쪽 [옮기기] · 모달 [일위대가 생성] 한 벌)
- Router: POST /hopyo/{키}/price 우리 금액 미리 셈(저장 없음 · Make.Move.pick · Lines.picks → Labor_Doc.calc 재사용)
- Sheet: [수정] = openHopyoCheck · 시트 아래 자리는 단가산출 블록 판(SheetSk) 몫으로 남김
- 안 살림: 맞음·별칭 일괄 체크 · 품셈으로 바꾸기 · 모두 고정으로(지운 서버 길 몫) · 사용안함(새 옮기기 길에 뺌 없음)
- 시험: 새 test_786_2_check_modal(우리 금액 예시 1 = 94,227 · 예시 3 입력 필요 → 0 · 쓰기 0 · 모달 · 새 배지 ·
  공용 import 만 · 날 button/input 0) · test_642_6 단언 = 줄 판 createJudgeRows → 모달 openHopyoCheck(까닭: 줄 판 몸 걷음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 07:26:37 +09:00
eomsangdon 343436d816 Merge remote-tracking branches 'origin/CODEX' and 'origin/sub_laptop_1' into main_laptop_1 2026-11-05 07:18:54 +09:00
eomsangdon 4d9e4530a1 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-05 07:18:53 +09:00
eomsangdonandClaude Sonnet 5.5 e23fec8c9c feat(786-1): 별도계상 줄 품목 고르기 없앰 — 후보 고정 줄 하나 · SC 합 밖 고정 줄
- Row.judge: 별도계상 줄은 M01 · 사전 · 장부 후보 안 찾고 고정 줄 하나(품목 「프로젝트에서」 표지) · 갈래 수 그대로
- Make: 별도계상 고정 줄 = 단가 0 · 메모 「별도계상 · 품목은 프로젝트에서」(합 밖)
- Picks.ts: apartPick 표지 셈
- 시험 642_3 · 642_5 · 642_6 Picks 몫을 새 꼴로 고침(옛 단언: 레미콘 자재 목록 별도계상 칸)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 07:17:50 +09:00
eomsangdon d922ad13f3 fix(est): match project costs and stored formulas 2026-11-05 07:12:08 +09:00
eomsangdon bcc570ff31 Merge remote-tracking branches 'origin/CODEX' and 'origin/안티그래비티' into sub_laptop_1 2026-11-05 07:07:41 +09:00
eomsangdonandClaude Opus 5.5 bb0f7134b5 knowledge(요구): 요구 645 — 수정 모달 옛 방식 + 새 기능 표기 · 별도계상 품목지정 없음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 07:04:50 +09:00
eomsangdonandClaude Opus 5.5 eae688ccb3 knowledge(요구): 분석 644 — 불러오기 [수정] 모달이 줄 판으로 바뀐 경위 · 구조물 조합 공용 부품 · 갈래 셋
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 06:37:15 +09:00
eomsangdonandClaude Opus 5.5 6fd55df168 knowledge(요구): 요구 644 — 일위대가 수정 모달 되돌림 검토(구조물 조합 공용 조심)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 06:31:10 +09:00
eomsangdon 1856aa897d feat(est): connect original quantity sheets 2026-11-05 06:19:15 +09:00
eomsangdon 91d5f478cc fix(stmate-test): resolve 5 UI/calculation issues found during browser control verification 2026-11-05 06:11:43 +09:00
eomsangdonandClaude Opus 5.5 d99f3a1fc1 knowledge(요구): 요구 643 — 검증 결과 아티팩트 공유 링크
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 06:10:58 +09:00
eomsangdon 60ccedcd9a feat(est): reproduce native tables and calculations 2026-11-05 06:07:05 +09:00
eomsangdonandClaude Sonnet 5.5 11d1f5116d test(785-8): 낡은 시험 둘 가름 — 울진소광 S1 해시 다시 굳힘 · 구조물 조합 제목 줄 단언 지금 꼴로
- test_621 울진소광 S1: 785-1(새 M02_Hopyo_Clause 출처 풀이)부터 단산 1479 「옆도랑파기(토사)」 출처 「건설품셈(옛) 8-2-3」 → 「건설품셈 공통 8-2-3」(같은 번호 형제 블록 1477 · 이미 공통) — 뜻 있는 변화 · 바뀐 칸 이 한 곳뿐 · 수 · 갈래 · 참조 그래프 그대로 · 이 해시 한 칸만 새로 굳힘(나머지 일곱 파일 · S2 칸 그대로)
- test_m02_layer_screen 구조물 조합 제목 줄: 738-2 · 740-2 가 제목 줄에 [초기화] [삭제](actions.reset · actions.del)를 끼운 뒤 낡은 단언 — 지금 꼴(reload › reset › del › save › 새 항목)로

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 01:56:07 +09:00
eomsangdon 1ca1940272 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/안티그래비티' into sub_laptop_4 2026-11-05 01:48:19 +09:00
eomsangdonandClaude Opus 5.5 4f36664df7 knowledge(계획): 계획 이관 782 · 784 · 785 — 건설 공통부문 기초 · 우리 품 · 엑셀 불러오기 다시 짬
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:47:54 +09:00
eomsangdonandClaude Opus 5.5 c8f3d244ae test(785-7): test_728_2 돈 아닌 기초 수 = 지금 M01 65(낡은 기대 21 · M01 기초 늘어 깨짐)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:32:48 +09:00
eomsangdonandClaude Opus 5.5 cf4e2a0339 refactor(785-7): 옛 맞춤 화면 TS 지움 · 옛 [저장] · [모두 고정] 단추 걷음 · 390 줄 판 높이
- 지움(git rm) 15 파일 3,318 줄: Hopyo_Check · CheckRows · Choose · WizardSk · SkApi · SkLines · logic/SkPick · logic/Draft ·
  Left · Lost · Pick · SheetMake · Kids · ToItem · Compare(모두 옛 확인 창 · 마법사 단가산출 · 안 이은 줄 길 몫 · 부르는 곳 0)
- 단추 판단: 엑셀 보기 [저장](체크 호표) · [모두 고정으로 들여오기] 화면 단추 걷음 → 만들기 = [옮기기] 하나 ·
  모두 고정 서버 길(Builder · build_all)은 B09 · 붙여넣기가 써서 남김 · 목록 체크 칸 · 모달 껍데기(openHopyoSheet · raise · lower) 걷음
- Import: 「안 이은 줄」 띠 · [안 이은 줄 고르기] · 남은 줄 셈 걷음 · [끝내기] = 물음(확인하고 안 옮긴 수 = 판정 고름 캐시 moveBody)
- Api: 옛 맞춤 타입 · 길(readHopyoOne · guessHopyo · buildHopyo · buildHopyoAll · leftHopyo · listHopyoPicks · hopyoSiblings ·
  buildHopyoItem …) 걷음 · POLL_MS · HOPYO_BUILT 옮겨 옴 · 목록 꼴 = 1차 판정 줄상태(Judge.view)
- Labor_Pick: withHopyo(실무 호표 덧줄) → withSangeun(찾아 넣기 창 표시만) · Detail · Table 부름 바꿈
- Ledger: unitField 옮겨 옴 · SheetNav: 옛 확인 캐시 칠 걷음 · UI_Page: [구조물 조합으로] 듣기 걷음
- CSS: 옛 확인 창 · 고르기 창 규칙 34 걷음(481 → 287 줄) · 390 폭 = 줄 판 열리면 위 칸 넓힘 · 줄 판 높이 바닥 16rem

시험 정리(까닭 = 지운 화면 파일만 읽음):
- 지움: test_729_3_hopyo_left_screen · test_746_2_hopyo_compare_screen · test_778_5_sk_pick_screen · helper_729_3 · helper_m02_hopyo_check · pick · seam
- test_m02_hopyo_screen = 좌측 판 · 끝내기 둘만 · 627 · helper_778_5 = SheetSk 판 바꿈만 · 785_4 · 721 · 717_4 · 720 · 739_1 = 지금 꼴로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:32:17 +09:00
eomsangdonandClaude Opus 5.5 48848bb287 refactor(785-7): 옛 맞춤 층 Python 지움 · 서버 새 길로 정리 · 낡은 시험 정리
- 지움(git rm) 10 파일 3,374 줄: M02_Hopyo_Finger · Ours · Option · Fit · Combine · Verdict · Line · SkLink · SkPatch · Basis
- Match: 흐름 몫(candidates · name_cands · fix_typo · clause · b_order · _logic_specs · _line_mod · units_of · _state · view · guess · picks · chain) 지움 · 이름 도구 몫 · line_key 남김
- Words: conds · cond_score · close · cosine · jacc 지움 / Shares: sections 지움(trade · eq_key 남김)
- 옮김: Finger.kind_of → Fixed.kind_of · SkLink(sk_line · kept · left · LEFT · WHY) → Sangeun · SkPatch 기계 몫(MACH_IN · MACHINE · SOLO · _mach_logics) → Units.mach_logics
- Sangeun: 옛 /sangeun 몸(build · _sure · Maker.basis · judge · plan · make) 지움 · Kids: first · proposal · order 지움 · Build: build() · unlinked() 지움(모두 고정 Builder · build_all · _order · _origins 남김)
- Router: 옛 길 지움(GET hopyo/{키}/{번} · guess · hopyo-picks · hopyo-build · hopyo-unlinked · hopyo/{키}/sangeun) · GET hopyo/{키} = Judge.view(1차 줄 판정 줄상태 · 만든 · 블록 풀이 셈)
- Marks: 칠 = Judge.judged 만 · Forget: 맞춤 캐시 지움 줄 걷음 · Ledger: Ours.version 판 칸 걷음 · Item: Kids.first 걷음
- Row(덧): 단산 줄 = 장부 짝이 있어도 늘 「2차 필요」(장부 = 1차 후보 힌트만)

시험 정리(까닭 = 지운 길 · 모듈만 시험함):
- 지움 11 파일: test_m02_hopyo_finger · fit · golden · option · ours · basis · seam · flow · draft_calc · test_635 · test_628(지문 · 순위 · 옵션 거꾸로 · 맡은 절 · 옛 확인 흐름)
- 함수 지움: /sangeun · /hopyo-build · /hopyo-unlinked · hopyo-picks · 옛 줄 맞춤 보기를 부르던 것(572 · 718_1 · 729 · 739_1 · 777_1 · 778_5 · 780_1 · labor_national · 705_3 · m02_hopyo_match · 746_1)
- test_621 S2(지문 해시) 지움 · S1 남김 · test_m02_hopyo_build = 장부 길 시험만(PUT 으로 심음)
- 새 길로: 779_2(줄상태 = 1차 판정 · 칠 셈 전 None) · grid · stmate(hopyo-judge 줄 판) · 778_1 · 778_2(Units.mach_logics) · 642_6(Marks 원문) · 642_3 장부 짝 단산 줄 2차 지킴

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:15:20 +09:00
eomsangdon 267b5a693f feat(stmate-test): implement full STMATE w 8.0 UI, COSTN.SMT binary parser, and 100% lossless DBF/STC engine 2026-11-04 23:45:20 +09:00
eomsangdonandClaude Opus 5.5 0ec931dcde fix(785-6): 화면 검증 흠 고침 — 장부 짝 단산 줄 · 기초 칩 갈음 · 단가산출 탭 [옮기기] · 확인 · 만든 줄 칠
- logic/Hopyo_Picks.ts: 장부 짝 줄 제안 = 고른 것 모두(기초 둘 · 입력값 그대로)
- Hopyo_Rows.ts: 단산 줄 = 판정과 상관없이 블록 풀이 칩(장부 짝 제안 있음 줄도) · 이름 없는 후보 = 키
- Hopyo_Block.ts: 기초 칩 = 키로 끄고 켬(장부 고른 것 → 블록 풀이 바인딩 · 입력 필요 다시 보임)
- Hopyo_SheetSk.ts: 단가산출 판 아래에도 [옮기기](같은 캐시) — 호표 목록 판이 숨는 탭
- logic/Hopyo_SheetMap.ts · SheetNav · Sheet: 판정 화면 확인함 · 만든 SC 호표 줄 = 초록(밖 줄 그대로) · 확인 바뀜 = 다시 칠
- 시험: test_642_6 장부 짝 · 줄 칠 · helper_642_6 칠 셈 · helper_778_5 Rows 대역

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:39:51 +09:00
eomsangdonandClaude Opus 5.5 65bafe0050 feat(785-6): 785-5 실제 길 꼴로 맞춤 — 칠 = Judge.judged · 옮기기 덮음 물음 · 목록 셈 중 다시 읽음
- M02_Hopyo_Marks.py: 블록 판정 = M02_Hopyo_Judge.judged(셈 중 None = 옛 판정)
- Hopyo_Rows.ts: [옮기기] 409(이미 옮김) = 물음 한 번 뒤 덮음
- Hopyo_SheetSk.ts: 목록 셈중 = 3 초마다 다시 읽음(판정 · 고를 것 실림)
- JudgeApi · logic/Picks: 절 몫 = 수 · 목록 셈중 · 몸 덮음
- 시험: test_642_6 Marks 부름 대역 · helper_778_5 Check 대역

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:23:26 +09:00
eomsangdon fa08640b36 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_3 2026-11-04 23:20:11 +09:00
eomsangdonandClaude Opus 5.5 dcecc0b326 feat(785-6): 엑셀 보기에 줄 판 · 블록 판 · [옮기기] 이음
- Hopyo_Sheet.ts: [수정] = 시트 아래 줄 판(createJudgeRows · 그 호표 자리 뜀) · 오른쪽 판 아래 [옮기기](createJudgeMove) · 고름 캐시 한 벌 · 옮긴 뒤 목록 · 칠 · 단가산출 목록 다시 읽음
- Hopyo_SheetSk.ts: 단가산출 목록 = 새 블록 판정(hopyo-judge 목록 · 판정 · 고를 것 n · 표지 · 부름 · 만든 SK · 캐시 확인) · 줄 누름 = 그 자리 + 블록 판(createJudgeBlock)
- Hopyo_JudgeApi.ts: 목록 블록 줄 판정 · 고를수 · Block.ts 타입 흠 둘
- 낡은 시험 고침(까닭 = 785-6 이 옛 마법사 단가산출 칸 · 확인 창을 새 판으로 갈음): test_627_sheet_sk(판 바꿈 · 강조 · 뛰기를 새 목록으로 · helper_778_5 sheetSk 대역 새 꼴) · test_785_4 줄 판 자리 손잡이 · test_m02_hopyo_screen [수정] = 줄 판
- test_642_6: 엑셀 보기 이음 · 가운데 페이지 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:16:43 +09:00
eomsangdon e562818c4f Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-04 23:14:19 +09:00
eomsangdonandClaude Opus 5.5 a22af84b62 feat(785-5): 만들기 · 길 — 확인한 고른 것 → SC · SK · 장부 · 새 M02_Hopyo_Router · 판정 · 검산 · 옮기기
- 새 M02_Hopyo_Router — 옛 hopyo 길 모두 M02_Labor_Router 에서 옮김(옛 화면 785-7 까지 돎) · 새 길 hopyo-judge 목록 · 호표 · 블록 · hopyo/{키}/check · hopyo/{키}/move · main 등록 · 시동 warm · Fit.ensure 걷음 · 올림 때 엑셀 사전(Row.lexicon) · 블록 풀이 뒤 셈 · 끝내기 때 저장본 지움
- 새 M02_Hopyo_Judge — 1차 줄 판(Row) + 단산 줄 2차 풀이(Clause → Solve) · 엑셀 칸(이름 · 규격 · 단위 · 단가) · 블록 금액 · 절 몫 · 블록 풀이 파생 저장본(HP 폴더 _derived · 입력 지문 · 지문 다를 때만) · judged(folder, key) · 검산(입력값 = 엔진이 쓴 칸 모두 · 지역 · 자재지역 · 조종원직종 비움 · 맡은 절 단위 줄 · 몫과 품 대조 · 금액 = 일위대가 로직 줄과 같은 셈)
- 새 M02_Hopyo_Make — 고른 것 → SC 줄(같은 별칭 · 줄 본뜬 {호표, 차례, 행} · 수량 = 엑셀 × 몫 × 배 · 같은 기초 둘째 = 한 줄 SC 사본 · 별도계상 자재 · 이음 · 율 · 고정 · 참조) · SK 더한줄(바탕 없음 · SkUnit.adds · Sangeun.Maker.write) · 하위 먼저 · 입력 필요 · 단위 환산 못 정함 = 그 줄(블록)만 막힘 · 장부 고른 것 꼴 짝 · 이미 옮긴 것 = 덮음 없으면 409(남의 SC 덮지 않음) · 확인한 줄 몸(Lines) = Build 에서 옮김
- Build 나눔 — 모두 고정 길 · Builder · _order · _origins · build_all 그대로(B09 빌림) · 확인한 줄 몸 → Make.Lines
- SkUnit · Sangeun — SkPatch · Fit · Basis 부름 걷음(기계경비 기초 차례 SkUnit 으로 · 근거 꼬리표 = Clause.basis · 맞춤 셈 409 없앰 · 재료 고름 MO 됨) · Kids 가 _same_mark 가짐
- 시험 새 test_642_5_make(판정 · 검산 · 옮기기 · 예시 1 · 2 · 3 · 덮음 409 · 막는 길) · test_m02_hopyo_excel 마당에 새 Router 더함 · HOPYO_MAX_BYTES 자리(길 옮긴 까닭)
- 낡은 시험 고침: test_718_1 · test_729 「맞춤 셈 전 409」 뺌(맞춤 뒤 셈 안 기다림) · test_628 맞춤셈 캐시 넣기 뺌(근거 = 조항 색인) · test_729 근거 없음 꼴 · 고정 이음 유가 칸(785-3 LINKS 늘어난 까닭)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:13:53 +09:00
eomsangdon 8fde93b910 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_3 2026-11-04 23:10:54 +09:00
eomsangdon 8775e935dc Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-04 23:09:19 +09:00
eomsangdon 73f113ba34 feat(785-4): 불러오기 화면 껍데기 — 가운데 상세 페이지(마법사) 없앰 · 왼쪽 [불러오기] → 바로 본 칸 엑셀 + 오른쪽 목록
- 새 Hopyo_Import.ts(머리 막대 층 · 안 이은 줄 배지 · [끝내기] · 떠남 지움) · Hopyo_Wizard.ts 지움
- Hopyo_Side.ts = 올림 · 층 작은 고름(둘 이상일 때만) · 작업 중 줄 · 다른 엑셀 물음
- Hopyo_Sheet.ts = 몸(buildHopyoSheet) + 모달 껍데기 · 만들기 셋 → 새 Hopyo_SheetMake.ts · [삭제] 걷음 · 찾기 · 체크 부품으로
- 흠 셋: 안 이은 줄 0 오표시(빈 호표 번) · 맞춤 셈 중 멈춤(3 초 저절로 다시) · 좁은 판 줄 짓눌림(시트 아래 줄 판 자리)
- 시험: 785-4 지킴 시험 새로 · 낡은 마법사 · 모달 [삭제] · 좁은 꼴 인자 대조를 새 파일 · 새 길로 고침(Wizard 삭제 · SheetMake 이동 · narrow 걷음)
2026-11-04 23:08:39 +09:00
eomsangdonandClaude Opus 5.5 fa50fe05d3 feat(785-6): 블록 칠 새 판정 — Marks 가 Make.judged 저장본을 읽음 · SheetMap 제안 칠
- M02_Hopyo_Marks.py: 블록 판정 = M02_Hopyo_Make.judged(folder, key) 파생 저장본(785-5) · 고를수 실음 · 저장본 없으면 옛 맞춤셈 판정(785-7 에서 걷음)
- logic/Hopyo_SheetMap.ts: 기초로 풂 · 기초+덧 = 초록 · 줄 조합 · 고를 것 n = 파랑 · 일위대가 밖 글
- test_642_6_screen.py: 블록 칠 새 판정 · Marks 옛 길 대체 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:45:49 +09:00
eomsangdonandClaude Opus 5.5 0117dbdf2e feat(785-6): 판정 화면 새 파일 — 줄 판 · 블록 판 · 고름 캐시 · 새 길 타입
- Hopyo_JudgeApi.ts: 785-5 길 다섯 타입 · 부름(hopyo-judge 목록 · 호표 · 블록 · check · move)
- logic/Hopyo_Picks.ts: 고름 캐시 꼴 · 제안 = 미리 체크 · 호표 제안 모두 확인 = 다 풀린 제안만 · 입력 필요 남은 줄 막음 · 옮기기 몸 = 확인한 것만
- Hopyo_Rows.ts: 시트 아래 줄 판 — 1차 판정 칩 · 갈래 · 후보 복수 고름 · 수량 · 단위 환산 칸 · 엑셀 행 뛰기 · [확인] · [옮기기]
- Hopyo_Block.ts: 블록 판 — 절마다 기초 후보 · 맡은 절 · 입력값 칸(출처 꼬리표 · 입력 필요 · 엇갈림 · 기본) · 줄 대조 · 블록 판정 · 금액 참고 · [검산]
- 사전 ui_template_locale_m2_judge.ts · 자리 CSS Hopyo_Judge.css
- 시험 test_642_6_screen.py + helper_642_6_picks.cjs(북부청 예시 1 · 2 · 3 실제 풀이로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:41:42 +09:00
eomsangdon b2c2bb7ad3 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_1 2026-11-04 22:30:34 +09:00
eomsangdon a966ef3e91 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-04 22:28:19 +09:00
eomsangdonandClaude Opus 5.5 553c9169cd feat(785-3): 1차 줄 판정 · 엑셀 사전 · 후보 · 수량 환산 + 유가전력 MO 이음
- 새 M02_Hopyo_Row — 단가표 시트(자재단가 · 노임단가 · 기계경비총괄표 · 기계경비적용기준 · 단가산출 · 일위대가 총괄표 · STmate 목록표 코드) → 엑셀 사전(갈래 + M01 키 후보 한 번) · 세부행 4 단계(비고 번호 → 별도계상 → 단위 → 사전) · 후보 차례 장부 → M01 표마다(LB · MT · MO · EQ · FX) → M02 층 SK · SC · 판정 넷(제안 있음 · 2차 필요 · 후보 여럿 · 후보 없음) · 고른 것 꼴 · 수량 = 엑셀 × 몫 × 배
- factor 를 Row 로 옮김(Match.factor 는 부름만)
- Elem 유가 후보(oil_rows · oils · lost 유가) · Ledger 짝 고른 것 꼴 넓힘(갈래 · 키 머리 · M01 있음 · 율 비목 · 기준 재검사 · 옛 꼴 그대로)
- 고정 줄 이음 유가 MO(LINKS · GROUPS 재료 · 재료비 = MO 값) · 일위대가 자재 목록 MO 허용(Labor_Doc 두 줄 · 줄 수 그대로)
- 화면 찾아 넣기 「M01 전체」 탭 유가 줄(oil) · 일위대가 = 고정 줄 이음 유가 · 단가산출 = 더한줄 재료 요소 MO · TS GROUPS 짝
- 시험 새 test_642_3_row(북부청 415 줄 = 단산 268 · 일위 1 · 별도계상 64 · 재료 78 · 기계 3 · 율 1 · 사방 201 · 거창 68 모두 갈래 하나 · 사전 · 장부 짝 · 환산 · MO 고정 셈 · 율 TS 거울)
- 낡은 시험 고침: test_721 의 FindKind 글 대조에 oil 더함(종류 하나 늘어난 까닭)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:27:48 +09:00
eomsangdon 646eab6245 Merge remote-tracking branches 'origin/CODEX', 'origin/main_laptop_1' and 'origin/안티그래비티' into sub_laptop_1 2026-11-04 22:26:19 +09:00
eomsangdon 586fe3e163 Merge remote-tracking branch 'origin/dev' into sub_laptop_1 2026-11-04 22:26:16 +09:00
eomsangdon b635ee57ae Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-04 22:24:36 +09:00
eomsangdon 8715ec9206 feat: expand EST calculation coverage 2026-11-04 22:23:40 +09:00
eomsangdon 1cc9dca9fc feat(tools): STMATE 원본 DBF/SMT/STC/JB_R 직접 읽기·쓰기 브리지 서버 및 UI 연동 추가 (tools/stmate-test) 2026-11-04 22:23:34 +09:00
eomsangdonandClaude Opus 5.5 d516d09fd9 feat(785-2): Bind B2 · B3 · B4 785-1 꼴 맞춤 · Solve 풂 절 parts · 지킴 시험 test_642_2
- Bind: B2 선택값 줄 = 낱말 같음 고름(「사용횟수」 ≠ 「사용」) · B3 상수 이름 · 덧(90˚) · 단위 맞춤(sec = 초 · km → m) · 경사도 → 경사각도 · B4 식 분자 · 분모 {수|기호} + 대입
- Solve.parts: Formula.works 절 + Clause 후보 {키, 절} → 풂 절(절 표 없는 후보뿐이면 블록 통째)
- 새 test_642_2_bind_solve: 예시 1 · 2 · 3 + 단산46 · 39 입력값 · 출처 꼬리표 · 입력 필요 · 엇갈림 · 지어 넣은 값 0 · 품 같음 · defaults 규칙

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:23:07 +09:00
eomsangdonandClaude Opus 5.5 932acba909 feat(785-1): 출처 풀이 · 조항 색인 새 M02_Hopyo_Clause — 블록 근거(머리 › 몸 › 절 제목) · 같음 › 윗마디 · 절마다 나눔 · 결과단위 거름 · 준용 · 옛 번호 · 기초 없음 · 번호 없음 표지
- Basis 앞 몫(_ours · ours · clause · road_map · _grams · _sections · _named · _part · resolve · settle · has_logic · _has · no_basis) Clause 로 옮김 · Basis = 다시 내보내는 얇은 껍데기(순위 몫 candidates · merge · pick 은 785-7 까지 남김)
- Excel: settle 부름 Clause 로 · ReadEst 별칭 hopyo_basis → hopyo_est
- 새 지킴 시험 test_642_1_clause_formula(설계 642-A 5-3 첫 줄 기대 — 단산 10 · 46 · 24 · 47 · 39 후보 · 33 준용 · 6 옛 · 3 번호 없음 · 절 나무 · 상수 · 대입식 · 절 몫)
- test_628: hb = Clause(정본) · 여덟 파일 표지 수 「기초 없음」 18 → 3 · 「옛 번호」 121 → 120 — 782-1 · 782-2 · 782-3 새 M01 기초로 낡은 수(옛 Basis 로도 같은 실패 확인)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:21:59 +09:00
eomsangdon b0f22ba7b1 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/안티그래비티' into sub_laptop_2 2026-11-04 22:15:45 +09:00
eomsangdonandClaude Opus 5.5 bf140c29fa feat(785-2): 옵션값 읽기 Bind · 검산 판정 Solve · master_auto.defaults — 1차
- 새 M02_Hopyo_Bind: B1 규격 · 품명 괄호 · B6 같은 호표 레미콘 a-b-c · B2 선택값 줄 + 글에 없는 켜기 칸 = 아님 · B3 상수(기호표 · 이름 · 90˚ · 표 칸 = 기호) · B4 대입식(분자 · 분모 자리 + 단위) · B5 표 거꾸로 + 감률 배수 · 출처 꼬리표 · 엇갈림 · 안씀 · 입력필요 · 비움 · 기본 제안 · 엔진 몫 quantities(품만)
- 새 M02_Hopyo_Solve: 후보마다 바인딩 → 줄 짝(L · X · M · R) → 품 같음(자릿수 반올림 폭 · 0.5 %) · 절 · 블록 판정 · 덧 줄 Units · 금액 참고(입력 필요 없을 때 엔진 한 번)
- master_auto.defaults: 기본 칸 › 고르기 첫째 › 가드 0 › 0 을 품은 범위 0 › 표 첫 줄 › None(새 맞춤만 부름 · values · run_line 그대로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:15:36 +09:00
eomsangdonandClaude Opus 5.5 02cabeccf2 feat(785-1): 단가산출 블록 글 → 절 나무 · 상수 · 대입식 · 선택값 · 절 몫(새 M02_Hopyo_Formula) · Book 단산 블록 글행 · 절 칸
- 새 M02_Hopyo_Formula — tree · head · works · line_of · factors · find(순수 · M01 · 엔진 안 부름)
- ReadEst 글행 남김 · 절 나무 칸 · Excel Book 단산 keep 에 글행 · 절
- 꼴 = PLAN 785 이음 줄(785-1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:13:41 +09:00
eomsangdon a56da7bb2b feat(tools): STMATE 계산 사슬 및 제어 옵션 독립 테스트 웹앱 추가 (tools/stmate-test) 2026-11-04 22:08:28 +09:00
eomsangdon c7d2e99a25 Merge remote-tracking branches 'origin/CODEX' and 'origin/main_laptop_1' into sub_laptop_1 2026-11-04 22:02:09 +09:00
eomsangdonandClaude Opus 5.5 2afe579e15 knowledge(요구): 요구서 642 · 설계 642-A · 분석 642-B · C · D — 엑셀 불러오기 다시 짬
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:00:54 +09:00
eomsangdon 9d0ebb71da feat: expand EST test workspace 2026-11-04 21:58:59 +09:00
eomsangdon 173b719826 feat: add standalone EST test app 2026-11-04 21:48:51 +09:00
eomsangdonandClaude Opus 5.5 dcc0e1b75a knowledge(요구): 요구 642 원문 — 엑셀 불러오기 분석 · 매칭 로직 다시 정의 · UI 단순화 · 밤 맡김
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:25:23 +09:00
eomsangdon 3070c9e193 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-04 21:24:34 +09:00
eomsangdon f82b1ff14a auto: 2026-11-04 21:15 (ESD_LAPTOP) 2026-11-04 21:15:38 +09:00
eomsangdonandClaude Opus 5.5 d90df58f94 knowledge(요구): 요구 641 원문 — 전체 중지
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:43:49 +09:00
eomsangdonandClaude Opus 5.5 a8ed3390e9 feat(782-2): 건설 공통 1장 나머지 기초 — 계수 표 33 · 환경관리비 · 공사용수 로직 · 성격 칸 · check_master 목차 대조
- 계수_건설품셈_01장_적용기준.json 덧(CC000495~527): 규칙 · 계수만 절(1-2-2 · 1-2-3 · 1-2-4 · 1-2-5 · 1-2-6 · 1-2-8 · 1-3-2 · 1-4-1 · 1-4-2 · 1-5-1~10)마다 「규칙 글」 표(원문 글 그대로) + 수 표(단위 · 소수 표준 · 금액 단위 · 체적 변화율 L · C · 체적환산계수 f · 파쇄된 돌 10∼40% · 잡재료 2∼5% · 작업반장 · 공구손료 3% · 시공단위 품 소수자리 · 품 산정 식 · 예시 · 건설폐기물 발생량 · 공사용수) · 1-2-9 소규모 적용시공량 · 다수 규격 예시 = 계수로만
- 표 칸 「성격」 — 기존 1-3 · 1-4 표 33 + 1-2-9 둘(같은 성격 묶음 · 1-2-9 ↔ 1-4-6 작업시간 제한 한 성격) · 기존 표의 다른 글자 그대로
- 로직_건설품셈_01장_적용기준.json 덧: GC001192 건설폐기물 발생량(1-5-4 · 면적 × TON/㎡ × 폐콘크리트 증가율) · GC001193 공사용수(1-5-6 · 작업량 × 단위용수 × 잡용수율) · 원문 「-」 칸 = 줄 없음(멈춤)
- 1-2-9 소규모 · 1-4 품의 할증 로직 = 만들지 않음(PLAN 783-1)
- 빌더 덧 build_로직_건설품셈_01장_적용기준.py 나머지(+ _적용기준_나머지_표 · _적용기준_나머지_로직) · 키 = master_keys.issue(adopt 는 로직을 받지 않음)
- check_master 「목차」(check_master_toc.py) — 공통 1장 원문 절 목록 ↔ 마스터 전수 · 통째 빠진 절 · 로직 없는 품·표·식 절 · 1-2-9 · 1-4-2 = 783-1 로 미룸 표지
- 지킴 시험 test_782_2_common_ch1.py · test_782_1_carry_127.py 로직 줄 목록 = 앞 셋만 봄(같은 파일에 1-5 로직이 붙어 낡음)
- check_master 전후 같음(+ 목차 0) · 기존 로직 1513 셈 값 전후 같음 · 계산전부 1515

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:42:38 +09:00
eomsangdon 83270acdbc Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-04 20:41:37 +09:00
eomsangdonandClaude Opus 5.5 57af9be22f fix(784-1): 「우리 품」 잘못 보임 뿌리 고침 — 못 맞춤 빈칸 · 가드 칸 자동값 0 · 수 칸 짝 · 자재 단위 배 1 · 절 하나 덧 없음 · 엑셀 값 표지
- A 옵션 찾기 못 맞춤(수준 9 · 거리 ≥ 1) = 우리 품 · 입력 안 냄 · 「못맞춤」 표지 · 꼬리표 「→ –(우리 셈 조건 못 찾음)」
- B 자동값 「만약(X > 0, X, 기본)」 칸 = 나눗수 퍼뜨림에서 뺌(0 = 원문 기본) · 옵션 후보엔 옛 1 도 둠
- C 하나씩 조합 — 후보 ≤ 4 수 칸도 둘씩 짝
- D 엑셀이 값으로 넣은 「0 = 식」 칸 출처 「엑셀 값 · 식 아님」 · 꼬리표 「원문 식이면 품 · 금액(조건)」
- E 결과단위 「자재」 = 배 1 · 공/㎥ ≡ 공㎥
- F 품 비교 우리 품 × 대상 몫
- G 절 하나 블록 = 덧 로직은 후보만(이중 계상 막음)
- H 1위 옵션 수준 ≤ 2 면 그 입력을 대상 입력값으로
- I 규격 「L=…cm」 를 거리 칸에 안 넣음 · 수 고르기는 제 단위 붙은 수만 · 엑셀 수 「1 ㎡」 를 m 로 안 읽음
- 우리지문 · 맞춤 캐시 판 올림(FP 2 · CALC 3)
- 새 지킴 시험 test_635_hopyo_ours_qty
- 낡은 시험 둘(test_628 표지 수 18 · test_621 S1 울진소광 해시)은 HEAD 에서도 실패 — 782-3 마스터 합침 뒤 값 · 손대지 않음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:41:09 +09:00
eomsangdonandClaude Opus 5.5 a2b6e11661 knowledge(요구): 분석 639 — 견본 자동값 1 이 셈 길 한가운데(run_line) · 견본 ↔ 기본값 나눔 정책
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:40:20 +09:00
eomsangdonandClaude Opus 5.5 523a83ade3 knowledge(요구): 요구 640 원문 — 제1호표 합판거푸집 재료비 · 노무비 차 원인 분석(수정은 나중)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:30:01 +09:00
eomsangdon b753fbd5e7 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-04 20:17:47 +09:00
eomsangdonandClaude Sonnet 5.5 0b81a6c072 fix(784-2): 단위 환산 칸 — 몫 1 = 몫 없음 · 칸 한 덩이로 묶음 · 화면 확인 뒤 다듬음
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 20:17:12 +09:00
eomsangdonandClaude Opus 5.5 ef9fb96bb1 knowledge(요구): 분석 638 — 철근 현장조립 두 키 = 가공 + 조립 원문 두 절 · 조립 옵션을 블록 통째로 맞춤 흠
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:13:21 +09:00
eomsangdonandClaude Opus 5.5 6ffa353fea knowledge(요구): 요구 639 원문 — 값에 전부 1 을 넣는 게 문제
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:12:08 +09:00
eomsangdonandClaude Opus 5.5 8ddb5bbda7 fix(782-3): 8-2 시공능력 로직 「0 이면 기본」 가드 칸 — 견본 자동값 1 로도 엉뚱한 값 안 나오게(분석 635)
- 가드 칸에 범위 [0, 원문 끝] — 견본 자동값 = 0(원문 기본): E입력 [0, 1](원문 E 표 모두 1 이하) · 굴착기 싸이클입력 [0, 30](원문 3 표 13∼30초) · 디젤 해머 α입력 [0, 4.0] · 워터제트 α입력 [0, 1.20] · 펌프준설 q입력 [0, 387] · 그래브 f입력 · k입력 [0, 1.00] · 콘크리트 운반 배출 [0, 4] · 대기 [0, 10] · 하역 [0, 2]
- 범위 없는 가드 칸(트랜처 전진 · 후진거리 · 로더 주행거리 · 스테이빌라이저 혼합횟수 · 플랜트싸이클)은 1 이어도 기본의 3배 안 — 그대로
- 압쇄기 · 골재세척 q입력 뺌(원문 q 한 값 · 1 이면 60배 어긋남) · 스테이빌라이저 유효혼합폭 최대(로터폭 − 0.4, 0)(자동값 음수 막음)
- 유압 파일 해머 강관 Ta 표 「16이하」 ↔ β 표 「16 이하」 글자 맞춤(16m 이하 줄 못 찾던 흠) · 그래브 N값 고르기 「해당 없음」 끝으로(첫 값 = 표 줄)
- 지킴 시험 test_782_3 가드 칸 자동값 덧 · 기존 로직 · 표 · 키 그대로(로직_08장 새 줄만 바뀜) · check_master 전후 같음 · 계산전부 1513 멈춤 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:09:23 +09:00
eomsangdonandClaude Sonnet 5.5 6e24b50c94 feat(784-2): 확인 창 배 칸 → 단위 환산 칸 — 같으면 숨김 · 몫 줄 몫만 · 빈칸 추정만 입력 · 장부 같은 꼴
- 새 logic/M02_MasterTemplete_Hopyo_Unit.ts: unitView(숨김 판정 · 두 단위 · 추정 두께)
- CheckRows: 「단위 같음 ×1」 · 「엑셀 1 ㎡ = 우리 [ ] 본」 · 「두께 0.2m 로 추정」 · Ledger 같은 입력 꼴
- 지킴 시험 test_단위_환산_칸_보이기_784_2 + helper_m02_hopyo_unit.cjs

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 20:07:50 +09:00
eomsangdon 53e2c22df6 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_3 2026-11-04 20:06:04 +09:00
eomsangdon f9cfbef5e7 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-04 20:03:49 +09:00
eomsangdon 2c53f46fbf Merge remote-tracking branch 'origin/sub_laptop_3' into sub_laptop_1 2026-11-04 20:03:12 +09:00
eomsangdonandClaude Opus 5.5 4e3edf5eb6 knowledge(요구): 분석 635 — 우리 품 잘못 보임 뿌리 · 같은 흠 전수 · 배수 칸
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:02:40 +09:00
eomsangdonandClaude Opus 5.5 d389bca29b feat(782-3): 건설 공통 8-1-4 · 8-2 시공능력 기초 — 계수 표 146 · 로직 48 · 기계설비 13-2-7 로직 1
- 계수_건설품셈_08장_건설기계.json 덧(CC000349~494): 8-2-2 ~ 8-2-33 절마다 원문 md 표 전부 + 표 밖 글 속 상수(산정식 상수) — 기존 표 글자 그대로 · 절 차례 자리에 끼움 · 범위 값 = [아래, 위] · 로직이 가운데를 집는 표(죠 크러셔 · 콘크리트 운반 상수)는 아래 · 위 두 칸
- 로직_건설품셈_08장_건설기계.json 덧(GC001143~1190): 8-1-4 기본식 · 8-2 시간당 작업량 · 1본당 시공시간 40 · 수중펌프 설치 해체 품 1 · 작업량당 기계경비 6(시간당 경비 × 1/Q — 굴착기 · 불도저 · 로더 · 덤프트럭 · 롤러 · 대형브레이커)
- 로직_건설품셈_13장_플랜트설비공사.json 덧(GC001191): 13-2-7 아세틸렌량의 환산
- 정함: 원문 범위 값 = 0 이면 가운데 기본 · 입력으로 바꿈 · 조건 = 고르기 · 짐작 값 없음 · 기존 브레이커 능력 표(CC000065 · 067)는 범위 한 칸이라 가운데 자동 안 됨 — 작업능력 입력
- 새 빌더 build_로직_건설품셈_08장_건설기계.py(+ _건설기계_읽기 · 표 · 표2 · 표3 · 로직 · 로직2 · 로직3 · 로직_플랜트 · 로직_준설 · 로직_경비) · 키 = master_keys.issue(adopt 는 로직을 받지 않음)
- 지킴 시험 test_782_3_capacity_82.py — 실무 엑셀 Q 재현 굴착기 12 · 덤프트럭 4 · 롤러 · 임목파쇄 0.5% 안 · 영덕계류 셋 = 엑셀이 0.6∼0.8㎥ 줄 싸이클 씀 · check_master
- check_master 전후 같음(틀 · 본문 · 로직 · 조합 0 · 단위 27 · 배 7) · 계산전부 1464 → 1513 멈춤 0 · 기존 로직 1464 셈 값 전후 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:57:27 +09:00
eomsangdonandClaude Opus 5.5 a09c2f6b7f knowledge(요구): 요구 638 원문 — 소규모 · 할증 B09 에서 더함 · 예시 2 제1호표 철근 현장조립 두 키
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:51:04 +09:00
eomsangdonandClaude Sonnet 5.5 a3b27ef5f5 fix(782-1): 불러오기 끝내기 확인창 인자 자리 · 로직 편집 소유 선택지(현장·공용)
- 끝내기: 안 옮긴 단가산출 글을 본문 끝에 붙임 · 단추 글 자리 복구
- 소유: 서버 값(현장·공용)과 맞춤
- 시험: test_782_1_small_flaws

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 19:41:11 +09:00
eomsangdonandClaude Opus 5.5 000e79f431 knowledge(요구): 제안 634 — 소규모 · 할증 프로젝트 조정(제안만)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:39:25 +09:00
eomsangdonandClaude Opus 5.5 7176043804 knowledge(요구): 요구 637 원문 — 품 비교 흠 하나만 보지 말고 관련 로직 전수
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:38:04 +09:00
eomsangdonandClaude Opus 5.5 3620eaa8e4 knowledge(요구): 요구 636 원문 — 품 비교 열 0.5476 > 25 흠
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:36:26 +09:00
eomsangdonandClaude Opus 5.5 6658c9cd25 knowledge(요구): 요구 635 원문 — 야면석 소운반 우리 단가 ≠ 엑셀 · 오른쪽 판 수정 배수 칸
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:33:54 +09:00
eomsangdonandClaude Opus 5.5 103ecadcc2 knowledge(요구): 요구 634 원문 — 1-2-9 소규모 · 1-4-2 할증 = 프로젝트에서 고름 · M01 기초는 만들되 M01 · M02 넣을 때 경고 · 제안만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:23:20 +09:00
eomsangdonandClaude Opus 5.5 928bcecd49 knowledge(계획): 계획 이관 781 — 예시 1 야면석 소운반 출처 책 · 부문 가림 · 근거 1-2-7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:21:46 +09:00
eomsangdonandClaude Sonnet 5.5 02380bb7d1 fix(781-2): 예시 1 근거 = 건설 공통 1-2-7 인력운반(지게) — 조항 같음 덤 · 조항 점수 · 근거 칩 툴팁
- Fit: 인용 조항 같음 로직에 이름 1위와 같은 덤(fuse) · 조항 가중 1.5 → 2.0 — 북부청 임도 단산10 1위 GF000214 야면석 채집 → GC001140 인력운반(지게) · 골든 1위 162 → 163(사람 120 · 새 43) · 다른 점 틀림 0 → 1(N006 정답표가 짐작 GF000280 인 줄 — 새 로직이 맞는 근거)
- 화면: 근거 칩 툴팁 로직 null 일 때 「null · 기초 없음」 → 「기초 없음」
- test_628: 예시1 시험을 새 뜻(근거 GC001140 · 기초 없음 표지 없음)으로 · 표지 수 19 → 18(공통 1-2-7 인용 블록은 여덟 파일 중 단산10 하나) · 우리 지문표 없으면 만들고 시작

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 19:06:36 +09:00
eomsangdon 9f86977293 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-04 18:46:36 +09:00
eomsangdon e43d8e27ca Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-04 18:44:51 +09:00
eomsangdonandClaude Opus 5.5 187fda1055 feat(782-1): 건설 공통 1-2-7 운반 기초 — 계수 표 여덟 · 인력운반 로직 셋
- 계수_건설품셈_01장_적용기준.json 1-2-7 표 덧(CC000341~348): 소운반 거리 · 인력운반 기본공식 상수 · 지게운반 t · V · 벽돌운반 · 인력운반(기계설비) 왕복평균속도 · 준비작업시간 · 경사지 환산계수 α · 화물자동차 적재량 — 옛 표 목록이 1-2-5 밑에 붙였던 1-2-7 표 바로잡음
- 새 로직_건설품셈_01장_적용기준.json(차례 13 · 공통 2장 앞): GC001140 인력운반(지게) · GC001141 인력운반(기계설비) · GC001142 벽돌운반
- 지게 = N = V × T ÷ (120 × α × L + V × t) 또는 운반회수 직접 · 보통인부 = 무게 ÷ 1회 운반량 ÷ N · 무게 0 = 1-2-4 3 단위중량 표
- 새 빌더 build_로직_건설품셈_01장_적용기준.py · 지킴 시험 test_782_1_carry_127.py(재현 넷 0.547619 · 0.163556 · 0.654222 · 0.0132 · check_master)
- 기존 표 · 로직 글자 그대로 · check_master 전후 같음 · 계산전부 1461 → 1464 멈춤 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:44:25 +09:00
eomsangdonandClaude Opus 5.5 a8acaed6f5 feat(781-1 · 781-2): 출처 책 · 부문 · 옛 책 가림 · 「근거 조항 우리 기초에 없음」 표지(요구 628 · 629 · 630)
- 책 낱말 표(ReadEst.book_of) — 임도 · 사방 = 옛 책 「(옛)」 · 공통 · 토목 · 건축 · 기계설비 · 유지관리 = 「건설품셈 <부문>」 · 그 밖 건설품셈(부문 모름) 그대로
- 출처 풀이(Basis.settle · Excel.read_book 끝 한 곳 · 두 형식) — 건설품셈(부문 모름) 번호 → 블록 이름(안 맞으면 글 두 묶음)이 원문 제목 · 같은 번호 로직 이름과 뜻 낱말을 나누는 부문 하나 = 그 부문 · 둘 이상 = 모름 · 없음 = (옛) · 임도 옛 번호 → 지식DB 대응표 확정 한 대응(가 · 나 항 = 현행 윗마디 · 일대다 안 옮김)
- 조항 견줌(Basis.clause) 한 줄 — 책 또는 구분 같음 · (옛) = 없음
- 기초 없음 표지(Basis.no_basis) — 출처 조항(윗 · 아랫마디 포함) 로직 0 = 「근거 조항 우리 기초에 없음 — 건설품셈 공통 1-2-7 운반」 · 옛 번호 = 「옛 번호 — 대응 미확인」 · 맞춤셈 꼬리표 · 맞음 아닌 후보 글 「참고 후보 — 근거 조항 아님」 · 단가산출 근거 꼬리표 1위 대신 표지(1위 맞음이면 1위)
- 시험: 새 test_628 · test_621 단계 해시 다시 굳힘(달라진 칸 = 출처뿐 · S1 수 · 그래프 그대로) · test_m02_hopyo_basis · finger · stmate · excel 출처 기대값 새 책 꼴로(낡은 기대 「임도 = 산림품셈」 · 「건설품셈」 부문 없음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:42:07 +09:00
eomsangdonandClaude Opus 5.5 83860e7ac4 knowledge(요구): 대조 632 — 건설품셈 목차 ↔ 우리 구분 · 계산 대상인데 없음 65 절
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:31:40 +09:00
eomsangdonandClaude Opus 5.5 d65244b9a5 knowledge(요구): 요구 633 원문 — 기초 없는데 어떻게 로직을 고치나(순서 물음)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:29:17 +09:00
eomsangdonandClaude Opus 5.5 2999a66a9d knowledge(계획): 계획 이관 780 — 단가산출 탭 오른쪽 목록 · 일위대가 밖 단가산출
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:26:59 +09:00
eomsangdonandClaude Opus 5.5 52725e4165 knowledge(요구): 요구 632 원문 — 품셈 목차 ↔ 우리 구분 비교 · 1-2-2 부터 전체 = 기초 확보 대상 · 앞은 가이드
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:19:44 +09:00
eomsangdonandClaude Opus 5.5 226ee92ff7 knowledge(요구): 요구 631 원문 — 건설품셈 원문 공통부문 1장 있나 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:15:06 +09:00
eomsangdonandClaude Opus 5.5 b616541df6 knowledge(요구): 분석 628 — 야면석 소운반 1-2-7 · 출처 책 · 부문 가림 · 기초 없음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:14:16 +09:00
eomsangdon 68e35a5fa8 Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_3' into main_laptop_1 2026-11-04 18:12:47 +09:00
eomsangdonandClaude Opus 5.5 9018227d71 fix(780-2): 단가산출 목록 판 — 목록만 굴림(거르기 · 옮기기 막대 늘 보임) · 좁은 화면(900 이하) 쌓임 유지
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:12:15 +09:00
eomsangdonandClaude Opus 5.5 75db009175 feat(780-1): 일위대가 밖 단가산출도 판정 · 확인 · 칠 — 모든 블록 · 부름 칸 · 빈 블록 「줄 없음」(요구 627)
- Sangeun: 판정 · 확인 몸 = 모든 블록(북부청 임도 49 → 95) · 줄마다 `부름 [호표 번]`([] = 일위대가 밖) ·
  밖 블록 = 이름 · 단위 블록 것 · 근거 없음 · 없는 번만 404 · 하위 밖 블록(울진소광 492 → 83)은 확인 목록에 함께 = 하위 먼저
- SkUnit: 줄 0 블록 = 판정 「줄 없음」 고를줄 1 → 확인해도 못만듦(빈 SK 없음)
- Marks: 블록마다 `부름` · `줄` · `어긋남` = 모든 블록(밖 포함) · 맞춤셈 `판정` 밖 = None 그대로
- 시험 고침(낡은 기대 = 일위대가 안 블록만): 778-5 units 49 → 95(안 49 따로) · 779-2 어긋남 + 밖 4 · 44 · 48 · 77 · 78 ·
  779-5 줄 칠 모든 블록 · 778-5 excel misses 밖 블록 5 · 새 시험 test_780_1_sangeun_outside(밖 확인 · 빈 블록 · 울진소광 · 여덟 파일 459)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:08:50 +09:00
eomsangdonandClaude Opus 5.5 5e5f5d4b8c knowledge(요구): 분석 629 — 건설품셈 공통부문 단가산출 기초 내재화 = 1장 · 8-2 누락
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:06:56 +09:00
eomsangdonandClaude Opus 5.5 8a5689c400 feat(780-2): 엑셀 보기 단가산출 탭 오른쪽 = 단가산출 목록 — 판 바꾸기 · 거르기 · 목록 ↔ 시트 · 두 벌 맞춤(요구 627)
- 새 Hopyo_SheetSk: 탭(역할) 따라 오른쪽 판 바꿈 · 일위대가표 = 호표 목록 그대로 · 단가산출표 · 상세 = 마법사 단가산출 칸 좁은 꼴 한 벌(처음 올 때 지음 · 다른 층 · 권한 없음 = 보기만) · 시트 칸 → 목록 강조 · 목록 줄 → 지금 탭 블록 자리
- WizardSk: 좁은 꼴 옵션 · 부름 칸(일위대가 n 곳 · 일위대가 밖) · 거르기(전체 · 안 · 밖 · 고를 줄 있음) · 같은 캐시 두 벌 맞춤 알림 · 옮김 뒤 HOPYO_BUILT(열린 엑셀 보기 칠 다시)
- SkPick: 줄 0 블록 = 「줄 없음」 확인 막음 · 거르기 · 끝내기 글 재료에 일위대가 밖 수
- SheetNav: role · blockAt · spotOf 내보냄 · SheetMap: 판정 없음 글 → 「일위대가 밖」
- Wizard [끝내기]: 막지 않음 · 물음창에 안 옮긴 단가산출 n(일위대가 밖 m)
- 시험: 새 test_627_sheet_sk(판 바꿈 · 목록 지음 · 줄 0 막음 · 캐시 공유) · test_778_5 끝내기 재료 꼴에 밖 칸 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:01:06 +09:00
eomsangdonandClaude Opus 5.5 00d28be647 knowledge(요구): 분석 627 — 단가산출 탭 오른쪽 목록 · 일위대가 밖 단가산출
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:53:19 +09:00
eomsangdonandClaude Opus 5.5 34288c1980 knowledge(요구): 요구 630 원문 — 원문 절 범위 = 다음 같은 단계 머리 앞까지
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:46:11 +09:00
eomsangdonandClaude Opus 5.5 3bf79b657f knowledge(요구): 요구 629 원문 — 건설품셈 공통부문 단가산출 기초 내재화 누락 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:44:08 +09:00
eomsangdonandClaude Opus 5.5 c81533eef4 knowledge(요구): 요구 628 원문 — 예시 1 제2호표 야면석 소운반 · 건설품셈 1-2-7 · 기초 로직 없음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:42:43 +09:00
eomsangdonandClaude Opus 5.5 850d2231c8 knowledge(요구): 요구 627 원문 — 사용자가 엑셀 보며 하나씩 맞춤 · 단가산출표 · 상세 탭 = 단가산출 오른쪽 목록 · 일위대가 밖 단가산출도 잇기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:33:48 +09:00
eomsangdonandClaude Opus 5.5 f2b49675a0 knowledge(요구): 요구 626 원문 — 4173 도 빌드
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 15:22:17 +09:00
eomsangdonandClaude Opus 5.5 9f6b7de26f knowledge(요구): 요구 625 원문 — 전역 깃 싱크
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 15:20:02 +09:00
eomsangdonandClaude Opus 5.5 a556a8a346 knowledge(계획): 계획 이관 776 ~ 779 — 경쟁사 출력 불러오기 단계 체계 · 호표 참조 · 줄 단위 단가산출 · 엑셀 세 보기 · 하이라이트
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:53:19 +09:00
eomsangdonandClaude Sonnet 5.5 838acdd8c1 refactor(778-5 D-3 덧): 안 이은 줄 창 기초 고르기 · SK 만들기 걷고 [단가산출 걸음에서 확인 ▸] 단추로 교체
- Hopyo_Left: 단산 줄 기초 select · makeSk 걷음 → 단추 하나(onSk — 마법사 단가산출 칸 그 블록 펼침 · 화면 가운데로)
- Hopyo_WizardSk: SkStep.show(번) 손잡이 추가 · Hopyo_Wizard: openLeft 에 onSk 연결
- Hopyo_Api: 옛 몸(`바탕`) makeSangeun · SangeunPick 걷음(서버 새 몸은 Hopyo_SkApi)
- locale: M02_LeftMakeSk · M02_LeftSkNone 걷고 M02_LeftSkGo 추가
- test_721_find_pick: 낡은 단언 `[views.root, addButton]` → 778-4 율 줄 추가 단추 포함 꼴
- test_746_2: 걷힌 기초 후보 필터 단언 → 걷음 단언 · test_729_3: 새 단추 단언 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 14:52:02 +09:00
eomsangdonandClaude Opus 5.5 3ffeff84c1 feat(778-5 D-5): 마법사 단가산출 줄 고름 — 제안 · 블록 [확인] 뒤 확인한 것만 옮김(요구 621 · 622 · 623 · 624)
- 단가산출 칸 = 서버 줄 판정(빈 확인) 블록 표: 판정 · 우리 금액 · 엑셀 금액(참고) · 근거 꼬리표 · 만든 SK · [확인] 칩(고를 줄 0 일 때만) · [줄] 펼침 · [보기]
- 줄 표(새 Hopyo_SkLines) = 묶음 · 이름 · 규격 · 매개값 · 상태 칩(엑셀 칠과 한 규칙 unitTone) · 꼬리표 · 방법 · 금액 · 차 · 고름(후보 · 찾기 · 율 기준 · 뺌 + 까닭 · 지움) · 엑셀 보기 뛰기
- 고름 · 확인 = 캐시 m02-hopyo-sk · [확인한 것 옮기기] = 몸 {확인: [{번, 고름}]} · 서버가 다시 셈 · 자동 확인 · 「모두 확인」 없음
- 순수 꼴 = 새 logic/Hopyo_SkPick · 길 = 새 Hopyo_SkApi(Hopyo_Api 안 늘림)
- 마법사 [끝내기] 막힘 글 앞에 「단가산출 확인 안 한 블록 n · 고를 줄 m」(Hopyo_Wizard 한 곳)
- 걷음: 옛 고름 대기 기초 고름 · 줄 비교(Compare.compareView · compareBrief · Api SangeunCompare · CompareLook · CSS) — 쓰는 곳 없음 · 못옮김 창 잇기 만들기(옛 바탕 몸 = 서버가 걷어 422) → 보기만 + 줄 찾기 단추 lineFind 로 다시 씀 · 안 쓰는 문구 39
- 낡은 시험 고침: test_746_2(줄 비교 · 못옮김 요소 단추 시험 걷음 — 기능 걷음) · test_729_3(candLabel · 옛 잇기 몸 기대값 걷음) · test_m02_hopyo_screen(sangeunStep 인자) · 새 test_778_5_sk_pick_screen + helper_778_5_sk_pick.cjs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:41:48 +09:00
eomsangdon 8380fa7334 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1
# Conflicts:
#	resources/tester/test_779_2_hopyo_marks.py
2026-11-04 14:17:12 +09:00
eomsangdonandClaude Opus 5.5 4cdb41f80b feat(778-5): 블록 = 줄 판정 모음 · 확인한 블록만 단위 SK(바탕 없음 · 근거 꼬리표) · 확인 목록 받는 길(D-3 · 요구 623 · 624)
- 새 M02_Hopyo_SkUnit: 줄 판정(Units 잇기 + 매개값 · 사용자 고름 {차례: 노임|기계|자재|기준|뺌} · 꼬리표 「내가 고름」) · 단가 = M01 지금 값으로 다시 셈(우리금액) · 엑셀 금액 차 = 참고 · 블록 판정 「옮길 수 있음」 | 「고를 줄 n」 · 단위 SK 더한줄 = 인력 · 기계경비 로직 줄 · 재료(손료율 든 수량 · 별도계상) · 율 줄(778-4 꼴 · 줄 목록 = 그 행 add:#k) · 하위 참조 · 값 고정 줄 · 저장 꼴로 다듬어 같은 내용 SK 지문 같음
- Sangeun: 기초 · SK · 고름 대기 갈림(SkPatch.plan) 걷고 단위 SK 하나 · 확인 [{번, 고름?}] 에 든 블록만 만듦 · 하위 단산 먼저 · 고를 줄 남으면 못만듦 · 일위대가 만들기가 부르는 Maker.plan(번) = 「확인 전」 고름 대기(자동 없음) · 별칭 제안 걷음(바탕 기초 없음)
- Router: sangeun 몸 바탕 → 확인(줄 수 그대로)
- 시험 새 뜻: test_729(기초 · SK · 고름 대기 · 계수 · 못옮김 잇기 · 729-5 차이 SK 바탕 길 = 바탕 없는 단위 SK 로 사라짐 → 확인 블록 단위 SK · 줄 고름 · 뺌으로 갈음) · test_718_1 · test_572 · test_779_2 · test_m02_hopyo_flow(확인 목록을 보내야 SK 생김 · 같은 파일 안 같은 내용 단위 SK 없을 수 있음 · 바탕 없는 SK = SC 에 참조 그대로)
- 새 test_778_5: 작업반장 = 건설업 · 규격 차례 무관 · 글자 규격 = 후보 · 손료율 · 설치비 · 제경비 율 줄 · 여덟 파일 줄 연결 표 · 확인 안 한 블록 안 만듦 · 고름 · 400 · 404 · 다시 올림 같은 SK 재사용 · 일위대가 확인 전 못 이음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:16:12 +09:00
eomsangdonandClaude Opus 5.5 68401ed02f feat(779-5): 블록 안 줄 칠 — Marks 블록 덧칸 줄 [[출처 행, 상태, 차]](D-2 줄 잇기 상태)
- 상태 = M02_Hopyo_Units.line(이어짐 · 고를 것 · 못 이음) · 셈 밖 = 밖 · 불리는 블록만
- 차 = 이어진 노무 줄의 엑셀 노임 ≠ M01 지금 노임(주황 「엑셀과 다름」 = 금액 참고 차만) · 기계 · 자재 참고 차는 D-3 셈 뒤
- SheetMap 상태 이름 「고를 것」 맞춤 · 실물 시험(북부청 임도 231 · 영월 135 줄 칠 = 단위 줄 수)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:58:28 +09:00
eomsangdon 1abe1673d7 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-04 13:44:17 +09:00
eomsangdonandClaude Opus 5.5 215cc1531d feat(778-5): 줄 잇기 한 벌 — 단위 줄 → 우리 계산 단위 + 상태(이어짐 · 고를 것 · 못 이음) · 일위대가 자재 맞춤 같은 함수(D-2 · 요구 622 · 623 · 624)
- 새 M02_Hopyo_Units: 노무 LB(같은 이름 여럿 = 건설업 먼저 · 작업반장) · 기계 EQ(Fixed.Linker) → 기계경비 기초 로직 줄(778-2) · 없는 규격 = 가까운 후보만 · 자재 MT(별칭 장부 › 이름 · 규격 › 바탕 이름 · 숫자 규격 · 이름에 붙은 규격 · 글자 규격 = 고를 것) · 손료율 방법 · 율 778-4 꼴(기준꼴 → 묶음 · 비목 · 줄 목록) · 하위 · 값
- Material: 숫자 규격 치수 차례 · 구분자 무관 견줌(dims) · 글자 규격 판별(lettered) · 단위 없는 줄(EST 블록) = 단위 안 봄
- Line._material = Units.material — 일위대가 자재 체크 제안 147 → 146(봉화 광명단조합페인트 「1종2류(철재)」 = 글자 규격 → 후보 · 요구 624-2)
- 여덟 파일 불리는 블록 줄 842: 노무 366/366 · 기계 180/190 · 자재 71/130 · 율 150/152 · 하위 3/3 · 값 1/1 · 줄 다 이어진 블록 156/206

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:43:51 +09:00
eomsangdonandClaude Opus 5.5 2260227b5d feat(779-5): 블록 안 줄 칠 순수 셈 — 블록 줄 [[행, 상태, 차]] → 출처 행만 · 머리 행 = 가장 나쁜 줄
- SheetMap `unitTone`(이어짐 초록 · 금액 참고 차 = 주황 · 후보 파랑 · 못 이음 빨강 · 밖 흐림) · 줄 재료 없으면 옛 블록 칠
- 서버 줄 상태(D-2 뒤 Marks 덧칸)는 다음 커밋
- 시험 새 test_779_5_block_line_tone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:35:28 +09:00
eomsangdonandClaude Opus 5.5 4231902d57 feat(778-5 D-6): 엑셀 셈 어긋남 띠 — 불리는 블록 재현 못 한 줄 · 덮음 실패 · 금액 차 · 까닭
- Marks 블록 덧칸 `어긋남 {줄합, 금액, 차, 덮음, 줄 [{행, 이름, 묶음, 셈, 금액, 차, 까닭}], 까닭}` · 읽기만
- SheetMap `missesOf` = 상세 시트 행 차례(덮음 실패 = 블록 머리 행 · 재현 못 한 줄 = 그 줄 행) · 칠 안 바꿈
- SheetNav 머리 막대 아래 띠(부품 createBanner) 「엑셀 셈 어긋남 n」 + [보기 i/m] 차례로 뛰기 · 그 행 까닭은 막대 글
- 북부청 임도 = 2 · 33(엑셀 합이 진동로울러 노무 뺌 · 차 2,637.1 · 3,418.7) · 64 · 95(글 꼴 밖 줄) · 81 맞음 · 영월 0
- 시험 새 test_778_5_hopyo_excel_misses · test_779_2 어긋남 한 줄 · helper_779_4 missesOf

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:34:02 +09:00
eomsangdon bc29bf6964 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-04 13:21:19 +09:00
eomsangdonandClaude Opus 5.5 022d6375e1 refactor(776-3): 지문 형식 모름 — 단위 줄 투영 한 벌 · 몫 한 벌 · 블록 단가 = 항목 · clause 대체 몸 · 오타 정규식 걷음 · 맞춤 캐시 = HP 지문 칸
- Finger — 풀이 두 벌(_text · _stmate) 걷고 블록 단위 줄 투영 하나(_units) · 글은 조건 · 옛 판 · 계수 · 「…의 N %」 만 · 셈 밖 줄 안 씀
- Shares — 몫 = 단위 줄을 절(제목 · 윗절)로 묶음 한 벌 · 식 다시 풀기(qty_of · sections_stm) 걷음 · trade = ReadEst.trade
- Elem.prices = 단위 줄 단가(글 다시 안 풂 · machines 갈래 안 건드림)
- Match.clause = Basis.clause 만 · 대체 몸 · _TYPO_CLAUSE 걷음(오타는 읽기 ReadEst.sources) · fix_typo = 사본(Line 이 부름)
- Fit.cache_path — HP 지문 칸(올린 엑셀 sha256 + 판) · 파일 그대로면 다시 안 읽음(lru) · 붙여넣기 HP 만 파일 해시
- 지문 전후 = 셈 단위 275 중 150 같음 · 다름 = 장비 절 · 글(쓰는 곳 없음) 100 · 고침(STmate 기계 두 묶음 · 거창 자재 · 끝 점 직종 · 옛 몫 금액 ÷ 단가 엉터리 · 제외금액 줄) — 골든 1위 줄마다 · 불리는 블록 206 갈래 · 1위 · 상태 전후 같음(162/177 · 기초 80 · SK 37 · 대기 89)
- S1 다시 굳힘 = 봉화 · 울진대흥 · 울진소광 자원(같은 코드 두 묶음 따로) · S2 다시 굳힘 125/275(위 까닭)
- 시험 고침 — finger(블록 = ReadEst.items · STmate 단위 줄 · 셈 밖) · fit 옛 HP(항목 · 글 없음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:20:17 +09:00
eomsangdonandClaude Opus 5.5 660e3318a9 feat(778-3): 단위 줄 정규화 — 단가산출 블록 · 일위대가 줄 = 노무 · 기계 · 자재 · 율 · 하위 · 값 줄(D-1 · 요구 622 · 623 · 624)
- 새 M02_Hopyo_UnitLine — 단위 줄 꼴 · 자체 검산(줄 재현 0.5 % · 줄합 = 블록 금액 덮음) · 값 줄 → 하위 가름 · 율 줄 기준꼴 · 기준줄 · 할증 풀이 · 일위대가 줄 투영(묶음 · 재현 · % 기준) · 두 어댑터 끝에 settle
- ReadEst.items — 줄 금액 · 율 · 값 줄 · 합계 · 소계 절 = 밖 · 끝 점 「28,500. ×」 · 이름 속 = · / 자재 · 「(지주)」 · 「\/㎡」 금액 꼬리 · 「직종 : P」 선언 줄 뺌 · 자재 손료율 표지 · 기계 비목마다 매개값 계수 · 절 = 제목
- Stmate — 같은 기계 코드 두 묶음 따로(울진대흥 1129 · 2119 · 울진소광 1448 단가 두 배 흠) · 비목마다 H 다르면 계수(봉화 덤프 (Cm-t1)/Cm · 영월 물탱크 ×0.722) · PR · RR 율 행(기준 표지 PA01M 꼴) · 이름 없는 율 행 = 위 라벨 줄 이름 · 기준글 · G 열 「* N'%'」 자원 = 율 줄 · 자재 손료율 · D 행 = 하위 · N 열 C = 밖
- Store.BOOK 3 — 읽기 꼴 바뀜(옛 판 HP = 다시 올림)
- 여덟 파일 불리는 블록: 줄 재현 811/836 → 836/842 · 덮음 201 → 204/206(남은 둘 = 엑셀 합이 진동로울러 노무 뺌) · 일위대가 줄 1,344/1,350
- 새 시험 test_778_3_hopyo_unit_lines

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:20:03 +09:00
eomsangdon 33951baeb0 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1
# Conflicts:
#	ui_template/ui_template_locale_m2.ts
2026-11-04 13:16:06 +09:00
eomsangdonandClaude Opus 5.5 fd9cce0b12 feat(778-4): 율 기준 줄 목록 열쇠@비목 — 그 줄 그 비목 몫(절 노무비 · 기계 노무 포함)
- 밑 공용 함수 rate_base ↔ rateBase · 검사 · 짝잃음 · B09 rows:n@labor(파이썬 · TS) · 굳힘 · 화면 열쇠 옮김이 비목 꼬리 그대로
- 여덟 파일 율 줄 152 중 기준 찾은 147 재현(인력 82 · 재료 35 · 절 소계 16 · 노무비 11 · 장비 2 · 자재+율 1)
- 시험 test_778_4_rate_line 29 · 전후 대조 148/148 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:15:13 +09:00
eomsangdonandClaude Opus 5.5 b8772a4172 fix(778-4): 율 줄 화면 = 그 비목 상자 % 줄 — 엔진 줄 비목 · 상세 덧줄 자리
- 율 더한줄 엔진 줄에 비목(묶음 = 경비 등) → 글 보기 · 상자가 「하위 로직」 아닌 그 비목 상자
- 단가산출 상세(hoView) = 율 줄을 덧줄 자리(율 · 「인력의 %」) · 수량 고침 = 율 그대로(화면배 안 나눔)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:09:58 +09:00
eomsangdonandClaude Opus 5.5 8cbde56a60 knowledge(요구): 요구서 621 9장 — 판단 여덟 답 반영 · 3번 예시 다시
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:05:18 +09:00
eomsangdonandClaude Opus 5.5 30ad267576 knowledge(요구): 요구 624 원문 — 아티팩트 판단 여덟 답(건설업 작업반장 · 규격 순서 무관 · 주변 행열 · 로직 기준 · 손율 = 세부 로직)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 13:03:21 +09:00
eomsangdonandClaude Opus 5.5 d9a581659f feat(778-4): 율 줄을 단가산출(SK)에 담음 — 고정 비율 더한줄 · 기준 묶음 · 줄 목록 · 율 위 율
- SK 더한줄 율 줄 허용(일위대가 비율 줄과 같은 꼴 `고정 비율 {기준, 비목}` · 수량 = 율)
- 기준 = 비목(노무비 · 재료비 · 경비) · 묶음(인력 · 재료 · 장비 = 그 줄 종류만 · 기계 노무 섞인 블록도 인력 줄만) ·
  줄 목록(cid · add:#k · 앞 율 줄 = 율 위 율) · 검사 = 자기 · 뒤 율 줄 막음
- 밑 공용 함수 `M02_Labor_Fixed.rate_base` ↔ 짝 `Labor_Calc.ts` `rateBase`(일위대가 비율 줄 · Expand 같이 씀)
- SK 셈 `_rated` = 엔진 한 번 돈 줄로 밑 → 율 줄 값묶음 → 엔진 다시(율 줄 없는 문서 = 한 번 · 값 그대로)
- B09 pct 밑수 `bundle:` · `rows:`(파이썬 · TS 짝) · SK 블록 율 줄 = pct
- 굳힘 — 바탕 없는 SK 율 줄은 한 줄로 안 펼침(참조 그대로) · 겹침 때 기준 열쇠 새 차례
- 화면 — [율 줄 추가] 창(새 `M02_MasterTemplete_Sangeun_Rate.ts` · 부품 상자만) · 율 줄 = % 줄로 보임 ·
  더한줄 지움 · 되살림 때 기준 열쇠 옮김
- 시험 — 새 test_778_4_rate_line(25) · 거울 헬퍼에 rateBase 길 · 전후 대조 SK 16 · 일위대가 118 · B09 대가 입력 14 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:59:48 +09:00
eomsangdon 0c7b0948f9 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-04 12:55:10 +09:00
eomsangdonandClaude Opus 5.5 c4c541838e feat(779-4 · 779-6): 엑셀 보기 창 세 보기 · 연결 칠 · 범례 · 뛰기 · 뒤로 · 열린 창 다시 씀
- 탭 셋 일위대가표 · 단가산출표 · 단가산출 상세 = 서버 역할 id(이름으로 안 찾음 · STmate 단가산출근거 뛰기 풀림) · 두 형식 같은 탭 글 · 원본 시트 이름은 막대
- 새 logic/M02_MasterTemplete_Hopyo_SheetMap.ts(순수 — 줄 칠 · 블록 칠 · 머리 행 = 가장 나쁜 칠 · 뛸 곳) · 「이어짐」 = 확인 창 체크 규칙(캐시가 이김)
- 새 M02_MasterTemplete_Hopyo_SheetNav.ts — 범례 createLegend · 맥락 단추(상세 · 목록 · 하위 호표 · 부르는 줄 차례로) · 뒤로 · 두 번 누름 = 첫 단추 · 다시 칠(목록 다시 읽음 · 캐시 고침 · 만들기) · 칠은 A 열 ~ 끝 열 · 띠는 A 열
- Hopyo_Sheet.ts = 잇는 줄만(580 → 609) · 줄 행 누름 = 그 줄 그대로 · 머리 행 = 호표 영역
- 779-6 — 같은 파일 창은 새 모달 대신 그 창을 앞으로 꺼내 그 자리로 · 닫기 = 제자리로 내림 · 마지막 격자 다시 씀(확인 · 고르기 창 [엑셀 보기] 모두 openSheet 한 곳으로 지나감)
- Hopyo_Check.ts saveDraft = 다시 칠 알림 한 줄(663 → 662) · Hopyo_Api.ts at 내보냄(676 그대로)
- 서버 Match.view — 목록 「후보」 중 이름 후보 안 찾은 줄만 다시 셈(뒤 셈 끝 뒤) → 확인 창 「없음」과 같음(빨강) · 북부청 임도 목록 응답 2.81 → 3.49 초(다시 셈 48 줄 · 없음 11)
- 시험: 새 test_779_4_sheet_map(꼴 표 · 블록 칠 · 실물 EST · STmate 칠 행 수 · 뛸 곳) · test_m02_hopyo_screen 779 지킴 더함
- 낡은 시험 고침: test_779_2 기대 「후보 ≠ 없음 허용」 → 「모두 같음」(까닭 = 이번 덧 셈) · 도우미 helper_m02_hopyo_check · helper_729_3_hopyo_left 에 document 대역(까닭 = saveDraft 알림이 document 를 씀)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:54:23 +09:00
eomsangdon 2f93dd8b11 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_1 2026-11-04 12:32:01 +09:00
eomsangdonandClaude Opus 5.5 443fcedbe5 knowledge(요구): 요구서 621 8장 — 사용자 확정(모두 줄로만 · 율 줄 담음 · 자동 없음) · 걸음 D-1~D-6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:31:05 +09:00
eomsangdon bcdc29415f Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-04 12:26:24 +09:00
eomsangdonandClaude Opus 5.5 32f5196b17 knowledge(요구): 요구 623 원문 — 모두 줄로만 · 율 줄 담음 · 자동 없음 사용자 확인 뒤 이동 · 안 맞는 부분 아티팩트
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:25:33 +09:00
eomsangdonandClaude Opus 5.5 697bd62c89 knowledge(요구): 요구서 621 7장 · 분석 621-D — 단가산출 = 세부항목 계산 단위(여섯 방법) · 줄 단위 분석 권함
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:18:49 +09:00
eomsangdon 7b4f5fc4f6 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_3 2026-11-04 12:13:51 +09:00
eomsangdonandClaude Opus 5.5 42230cf3d6 fix(778-2): 기계 잇기 — 연료 · 조종원 없는 기종은 그 기계경비 기초(272 · 273 · 276)
- SkPatch SOLO · _mach_logics: (주연료, 조종원) 없는 기종 = GC000273 손료만 · GC000272 조종원 없음 · GC000276 연료 없음(M01 로직이 부르는 것과 같음) · 까닭 = 부착용 집게를 GC000268 로 이으면 「없는 요소 MO:None」 셈 오류(ORCA 북부청사방 단산59 실측)
- EQ 630 중 570 셈 됨(남은 60 = M01 기계 자료 빈칸 · 이 걸음 밖) · 여덟 파일 장비 더한줄 180 줄 모두 셈 됨 · 기초 80 · SK 37 · 고름 대기 89 그대로
- test_778_2: 부착용 집게 = GC000273 · 셈 됨

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:11:00 +09:00
eomsangdonandClaude Opus 5.5 3a1ce9ca4f refactor(776-2): 엑셀 한 번 열기(격자 = 읽은 책) · 줄 갈래 한 규칙 · EST 블록 금액 · 항목 · 옛 판 HP 409 「다시 올림」
- Excel — read_book 이 연 책(xls = formatting_info · 목록 시트까지)을 Grid.read 가 이어 씀(opened) · 거창 여는 횟수 2 → 1 · 여덟 파일 올림 시간 17.8 → 7.8 초
- Excel.kind_of = 두 형식 · 붙여넣기 한 규칙(STmate 코드 머리 · EST 칸 · 끝에 노임 올림) · Stmate._kind 걷음
- Excel — EST 블록에 금액 · 항목(ReadEst) 실음 · Basis 대신 ReadEst 직접
- Grid — 읽은 책 그대로 · xlsx 열 폭은 보기 시트 머리 <cols> 만(sheetData 에서 멈춤) · 여덟 파일 격자 결과 전과 같음
- Store.read 다른 판 = 409 「다시 올림」 · Forget.finish 는 옛 판도 끝냄(old=True)
- S1 · S2 해시 그대로 · test_718_1 옛 판 열기 409 · 끝내기 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:09:24 +09:00
eomsangdonandClaude Opus 5.5 b665269093 fix(778-1): 엑셀만 장비 — 괄호 낱말로 하나면 그 기계를 고르기에 둔 기계경비 기초 바로
- SkPatch._add_machine: 기계가 하나로 가려지면 옵션 거꾸로 찾기 없이 _mach_logics 첫 기초 + 기계 입력 · 까닭 = GC000268 옵션 거꾸로 찾기가 규격 수로 기계 고르기를 좁혀 굴착기를 놓치고 GC000267(굴착기(무한궤도) 기계경비)로 떨어짐(타이어 줄 17)
- 굴착기(타이어형) 줄 44 = 모두 GC000268 + 0211-0060 · 기계 못 찾음 4 줄 이음(트럭탑재형 크레인 5ton · 진동기 · 동력분무기 · 대형 브레이커 0.7) · 기계경비 잇기 176 → 180 / 183
- 여덟 파일 기초 80 · SK 37 · 고름 대기 89 그대로 · SK 셈 오류 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:03:03 +09:00
eomsangdon a363bbc63a Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_2' into sub_laptop_1 2026-11-04 11:56:46 +09:00
eomsangdon f0506c0628 Merge remote-tracking branch 'origin/dev' into sub_laptop_1 2026-11-04 11:56:44 +09:00
eomsangdonandClaude Opus 5.5 026f6c667a fix(778-2): 못옮김 기계 잇기 = 기계경비 기초 로직 줄 · 목록 밖 기계 셈 문맥 고침
- SkPatch LINK 기계 = 로직 · _Plan.link: EQ 키 → 기계경비 기초(그 기계를 고르기에 둔 기초 먼저) + 입력 기계 = 원문번호 · EQ 키 바로 더한줄 셈 오류 없앰
- master_pick resolve · machine · master_formula _inputs: 셈에 넘어온 마스터로 기계 찾음 — 까닭 = mp.EQ ContextVar 는 마스터를 지은 문맥에만 있어 서버 둘째 요청(스레드 풀 · 새 문맥)부터 목록 밖 기계(굴착기(타이어) 0.6㎥ 등)가 「고르기 밖」으로 막힘 · ContextVar 는 대신 길로 남김
- 새 시험 test_778_2_hopyo_machine_link(잇기 뒤 SK 셈 · 서버 두 요청 · 문맥 밖 스레드)
- 여덟 파일 기초 80 · SK 37 · 고름 대기 89 그대로 · SK 셈 오류 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:54:09 +09:00
eomsangdonandClaude Opus 5.5 4b3e9016be feat(776-2): 실무자료 HP 에 읽기 꼴 판 찍음 — 다른 판 = 지난 것(목록 숨김 · 다음 올림 때 끝냄)
- Store.BOOK = 2 · save 가 「판」 찍음 · stale = 하루 지남 또는 판 다름
- 작업 중 열기 409 「다시 올림」은 Forget.finish 옛 판 읽기 한 줄과 함께(받기 뒤)
- 시험 test_718_1 판 지킴 한 개

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:51:33 +09:00
eomsangdon 0fbd1b1c91 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-04 11:48:14 +09:00
eomsangdon 54653eeeef Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-04 11:48:05 +09:00
eomsangdon 3d25775c05 Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-04 11:48:02 +09:00
eomsangdon 173a53c595 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-04 11:48:01 +09:00
eomsangdon ea4cb92e79 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-04 11:47:59 +09:00
eomsangdonandClaude Opus 5.5 c4cdc66de3 knowledge(요구): 요구 622 원문 — 단가산출 = 노무 · 경비 · 자재 세부항목마다 계산 방법 · 이것으로 분석 · 778 다시 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:47:09 +09:00
eomsangdonandClaude Opus 5.5 eaa8064f1a feat(777-1): 호표가 다른 호표를 부를 때 = 일위대가 둘 — 하위 함께 만들기 · 같은 SC 재사용 · 결과 글 · 납작 길 막음
- 부모 저장 때 확인 안 된 하위 호표(깊이 끝까지)를 하위 먼저 그 체크대로 함께 만듦(409 「먼저 확인」 걷음)
  · 서버 = 체크 제안 몸(M02_Hopyo_Kids.proposal ↔ 화면 bodyFor 짝) · 화면 = 확인 창 캐시 고름(Hopyo_Kids.ts)
  · 하위 실패 = 409 「하위 제N호표 실패 — …」 · 하위 · 하위남은줄 → 결과 표 [고르기]가 그 하위를 엶
  · [모두 고정] 연쇄 실패 까닭 따로 · 미리 보기는 하위 호표 줄을 이음으로 셈
- 같은 엑셀 다시 불러오기 = 같은 이름 · 같은 내용(수량까지) SC 재사용 — 본뜬.또(SK 꼴) · 끝내기는 그 몫만 뺌
- 결과 표 글 「SC-12 · 하위 제4호표 SC-11(사본 담음)」 · 확인 창 호표 줄 글 「하위 일위대가 SC-11 사본」
- [구조물 조합으로] 도 하위 먼저(못 만들면 지금처럼 고정 줄) · 붙여넣기 · B09 통째 = 이미 하위 먼저(안 고침)
- 엑셀 대조(CUTS · _excel_round · excel_given · compare) → 새 M02_Hopyo_Compare.py(Build 608 → 540)
- M02_Labor_Router 병행 흔적 가드 걷음(_later · inspect · 몸 칸 새로)
- STmate [엑셀 보기] 단가산출근거로 감(형식별 블록 시트 이름)
- 낡은 시험 고침: 「제46호표 먼저 확인」 409 · 글 → 하위 함께(777-1 정함) · 718-1 실패 = 남은줄 또는 하위 ·
  화면 시험 StaleError 안내 → unmadeKids · leftAt · 새 지킴 시험 test_777_1_hopyo_kids

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 09:24:48 +09:00
eomsangdonandClaude Sonnet 5.5 f9aa8832bb feat(779-2): 불러오기 엑셀 하이라이트 재료 — 목록 호표마다 줄상태 · 만든 SC 키 + 새 hopyo-marks 길(블록 판정 · SK · 갈래 · 못옮김, 읽기만)
- Match.view 목록: 호표마다 줄상태 [[차례, 상태, 체크]] · 만든 (이미 센 줄 상태 실음 · 덧 셈 0)
- 새 M02_Hopyo_Marks.py: 블록 [{번, 판정, SK, 갈래, 못옮김}] = Sangeun.made + Fit.result 읽기만 · 머리에 칠 꼴 대응표
- Router: GET /layers/{layer}/hopyo-marks/{key} (파일 끝에 한 덩이)
- 시험: test_779_2_hopyo_marks (줄상태 수 = 줄 수 · 읽기만 = 파일 변화 0 · 판정 = 캐시)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 08:55:56 +09:00
eomsangdonandClaude Opus 5.5 560e2e0928 fix(778-1): 장비 → EQ 괄호 낱말 규칙 — 굴착기(타이어형) = 타이어 기계 · 부속 뺌
- Elem: 괄호 낱말(타이어 · 무한궤도 · 습지 · 트럭탑재) 어긋남 · 부속(자동덮개 · 부착용 · 콤팩터 · 습지) 깎음 · narrow · machine(하나로 가름)
- Fixed Linker._machine: 같은 갈래 · 규격 여럿 = 괄호 낱말로 가름
- SkPatch._add_machine: 기계 = 괄호 낱말로 가른 그 기계 · 기계경비 기초 = 그 기계(같은 이름)를 고르기에 둔 기초 먼저
- 여덟 파일 장비 줄 잇기 59 → 172 / 183(32 → 94 %) · 굴착기(타이어형) 42 줄 = 세 길 모두 타이어 · 기초 80 · SK 37 · 고름 대기 89 그대로
- 새 시험 test_778_1_hopyo_machine

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 08:49:46 +09:00
eomsangdonandClaude Opus 5.5 0c2f12c23f refactor(776-2): EST 단가산출 읽기 어댑터 떼어 냄 · 두 형식 블록 항목 · STmate 블록 단가 · 금액
- 새 M02_Hopyo_ReadEst.py — Basis.read(엑셀 「단가산출」 블록) · trade_names · sources 옮김 + 글 → 항목 풀이(L 직종 · X 장비 · M 자재 · 줄 율에 기대지 않는 것만)
- Basis — 근거 후보만 남김 · 옛 이름(read · SHEET · GLINES · GCHARS · sources · trade_names)은 그대로 다시 냄
- Stmate — 블록 금액(머리 줄 C~F) · 항목(자원 + 단가 I~L · 금액 C~F · 기계 셋 줄 더함 · 행) · 항목 단가 639 중 635
- 항목만으로 지문 인력 · 장비 · 자재 다시 세움 = 지금 지문과 544 쌍 모두 같음(tmp 대조 · 776-3 길)
- S1 · S2 해시 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 08:48:10 +09:00
eomsangdonandClaude Sonnet 5.5 1c1d756a1e feat(779-1): 격자 세 보기 — 목록표 둘 · 보기 · 원본 · 목록 · 줄 행 대응
- Grid SHEETS 에 단가산출총괄표 · 단가산출근거목록표 추가(탭 차례 일위대가표 · 단가산출표 · 상세)
- 응답에 보기 {역할: 시트 id} · 원본 · 목록 {호표, 단산: [{번, 행}]} · 줄 [{번, 차례, 행, 단산?, 일위?}] 더함(옛 칸 그대로)
- STmate 목록은 숨은 COD 코드로 짝(차례 번 ≠ 블록 번)
- Excel._sheets 에 wanted 인자만(기본 그대로)
- 시험: test_779_grid_views 새로(여덟 파일) · test_m02_hopyo_grid 시트 목록 기대값 세 장으로 고침(탭이 늘어서)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 08:46:50 +09:00
eomsangdonandClaude Sonnet 5.5 67c1d1f13d feat(779-3): 공용 격자 칠 옵션 — 칸 data-tone(success · warning · info · danger · muted · mine 띠) · repaint() · 읽기만 시트 탭 「+」 숨김
- 안 주면 data-tone 없음 · 보이는 칸만 부름 · 색은 --color-* 만
- 새 시험 test_779_3_grid_fill.py

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 08:38:33 +09:00
eomsangdonandClaude Opus 5.5 9d26e5fcd6 test(776-1): 경쟁사 출력 단계 고정 시험 — 여덟 파일 S1(정규화 투영 해시 · 수 · 갈래 · 호표 참조 그래프) · S2(셈 단위 275 지문 해시)
- 새 test_621_hopyo_stages.py + fixtures/m02_hopyo_stages.json · 15 초 · --write 로 새 판 굳힘
- 투영 = 지금 HP 칸만 — Book 에 칸 더함은 그대로 · 빼기 · 이름 바꿈은 깨짐
- helper_m02_hopyo_golden 죽은 갈래 걷음 — EST 블록 글 다시 채움(읽기가 이미 글 붙임 · 223 블록 모두)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 08:36:49 +09:00
eomsangdonandClaude Opus 5.5 4e0ea28aec knowledge(요구): 요구서 621 · 분석 셋 — 경쟁사 출력 불러오기 단계 체계 · 요소 기준 연결 · 엑셀 세 보기 · 호표 참조 · 하이라이트
PLAN 776~779 짝 · 정함(브레인) 여섯 · 사용자 물음 둘(778-3 · 4 만 기다림)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 08:30:09 +09:00
eomsangdonandClaude Opus 5.5 cfd1c6070d knowledge(요구): 요구 621 원문 — M02 경쟁사 출력 불러오기 체계화 · 요소 기준 연결 · 엑셀 보기 · 호표 참조 · 하이라이트
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 08:02:22 +09:00
eomsangdonandClaude Opus 5.5 bc8d669e7f knowledge(요구): 요구 620 원문 — M02 엑셀 불러오기 분석 · 매칭률 · 매칭 로직 설명 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 07:04:44 +09:00
eomsangdonandClaude Opus 5.5 c3699e9e8c knowledge(요구): 요구 619 원문 — 브레인 메모리 정리 · 다음 일감 = M02 경쟁사 엑셀 불러오기 로직
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 06:52:54 +09:00
eomsangdonandClaude Opus 5.5 c24d81e267 knowledge(계획): 계획 이관 771 ~ 775 — 예정노선 수정 취소 · 중간 노드 긋기 · ☰ 서랍 · 평면계획선 방법 M
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 21:00:24 +09:00
eomsangdonandClaude Sonnet 5.5 42e505b325 test(756-1): 스냅 한 아이콘 뒤 낡은 글 대조 제거
Chrome 에서 빠진 「MC 를 고른 간격 배수에」 글 대조 한 줄 삭제

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 20:59:44 +09:00
eomsangdonandClaude Opus 5.5 a04a8f4223 feat(775-2): B05 예정노선 수정 끝 — 지금 편집기 곡선 수를 keep_curves 로 넘김 · 못 지키면 curve_note 안내 토스트
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:46:23 +09:00
eomsangdon 3c28a15ce5 Merge remote-tracking branch 'origin/dev' into sub_laptop_1 2026-11-03 20:43:44 +09:00
eomsangdonandClaude Opus 5.5 c7d63c1b78 feat(775-1·775-2): 평면계획선 예정노선 맞춤 — 곡선 수 그대로 교각점 · R 최소제곱 + 측점 · 최소 R 벌점 · 수정 끝 = 수정 전 곡선 수 틀
- 새 B05_Profile_Engine_RouteInitial_Fit: 성긴 최소제곱 맞춤 · 측점 벌점 1 → 1000 · 끝 확인(곡선 수 · MC 측점 어김 0 · R ≥ 최소 R) · 수정 끝 틀 고르기(예정노선 거리 덜 늘어나는 합침)
- merged_follow: 합치기 뒤 측점 맞춤 대신 맞춤 · 끝 확인 못 지나면 지금 측점 맞춤으로 물러남 · keep_curves
- /route/follow: 수정 전 곡선 수 = 본문 keep_curves 또는 저장 노선 곡선표 · 응답 keep_curves · curve_note
- 773 여덟 꼴 평균 최대 26.5 → 6.9 m · 평균 3.80 → 1.24 m · N 유지 8/8 · 측점 어김 0 · R 미달 0 · 물러남 0
- 지킴 시험 test_775_route_fit 새로 · test_758_1 그대로 통과(입력 덧점 정리로 체인 · 추적 같은 선)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:42:48 +09:00
eomsangdon d575fa25e5 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-03 20:41:02 +09:00
eomsangdonandClaude Opus 5.5 f6f29fdee1 feat(774-2): B05 예정노선 = 늘 노선 하나 — 끊긴 토막은 끝끼리만 붙고 맞닿으면 한 줄
- 한 선 페이지(기능 표 branch 숨김) 긋기 붙을 자리 = 다른 토막 끝만(가운데 노드 · 선 위 = 안 붙음 → 갈래 안 생김)
- 긋기 마침 = 맞닿은 토막 모두 이음(joinTouching · 꼬리↔머리 · 꼬리↔꼬리 · 머리↔머리) · D03 = 지금대로
- 끊긴 채 수정 끝냄 = 기존 막음 · 안내 그대로
- 시험: test_771_2 에 joinTouching 대조 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:40:22 +09:00
eomsangdonandClaude Opus 5.5 2a67a7092d feat(774-1): 다시 긋기 = 중간 노드 고름으로 켬 · 다른 중간 노드에 이어야 끝
- 노드 속성 판 · 우클릭 = 중간 노드에 [다시 긋기](선 토막 고름의 [다시 긋기] 뺌) · B05 긋기 중 중간 노드 누름 = 그대로 켬
- 끝 = 같은 선 다른 중간 노드만(rejoinMiss) · 시작 노드 · 끝점 = 못 찍음 + 안내(계속 긋기) · 빈 곳 = 점 찍고 계속
- 고리: 시작 노드 · 이을 중간 노드 · 마우스 올린 중간 노드(paintRejoin · --draw-map-*) · 옛 구간 흐림(dim) 뺌
- 시험: test_771_2 에 끝 판정 · 중간 노드 대조 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:31:21 +09:00
eomsangdonandClaude Opus 5.5 bc739cbcc2 knowledge(요구): 요구 618 원문 — B05 예정노선은 늘 한 줄
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 20:30:11 +09:00
eomsangdonandClaude Opus 5.5 258a59ade5 knowledge(요구): 요구 617 원문 — R 개수 = 수정 전 평면계획선 곡선 수 · 방법 M 둘 다
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 20:29:12 +09:00
eomsangdonandClaude Opus 5.5 9ab3ae5035 knowledge(요구): 분석 773 — 평면계획선 예정노선 맞춤 · 차이 주범 = 합치기 · 측점 맞춤 · 갈래 9 시험 · 권함 M(교각점 · R 맞춤 + 측점 벌점)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 20:26:06 +09:00
eomsangdonandClaude Opus 5.5 ea6207d587 knowledge(요구): 요구 616 원문 — 다시 긋기 = 노드 고르면 켜짐 · 다른 중간 노드에 이어야 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 20:21:35 +09:00
eomsangdon f2322854c1 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-03 20:06:24 +09:00
eomsangdonandClaude Opus 5.5 f96907fb8a fix(772-1): ☰ 서랍 진행 단계 · 바로가기 칸 빠짐 — 진행 판 공용 자리에서 맡김 · 안 맡긴 쪽 기본 목록
- 까닭: B03 파일입력 = 진행 판을 직접 조립(createWorkflowOverlays)해 서랍 맡김(setDrawerSteps)이 빠짐 ·
  B02 · B10 · B11 · D01 · E01 = 일반 판이라 맡기는 쪽이 없음 · B01 = 10-26 새 틀(e505f4014)에서 옛 ☰ 구역 바로가기를 걷음
- 진행 판(createWorkflowOverlays)이 progressContent 를 받으면 서랍에 맡김 — 워크플로 판 · B03 한 자리
- ☰ 를 열 때 맡긴 목록이 없으면 기본 목록 = 🏠 대시보드 + 권한별 M01 · M02(mountSettingsDrawer 와 같은 한 벌) · 늦게 온 쪽 목록이 이김
- 터치 켬 서랍 진행 단계 줄 40 → 44(옆 규칙이 더 세어 덮던 것)
- 시험: helper_280_2 의 app_shell 대역은 workflow_layout 이 더는 안 불러 지움 · helper_359_2 에 overlay 용 대역 더함 · test_772_1 새로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:05:47 +09:00
eomsangdonandClaude Opus 5.5 8ec6aea315 knowledge(요구): 요구 615 원문 — 예정노선 수정 뒤 평면계획선 차이 · R 개수 유지 · 제약 · 스냅 절대 · 시험하며 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 20:00:12 +09:00
eomsangdon 16a1b10b9d Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-03 19:49:23 +09:00
eomsangdonandClaude Opus 5.5 df879ffcb9 feat(771-1·771-2): B05 예정노선 수정 — 중간 노드에서 긋기 · 닿은 노드 사이 갈음 · 지움 뒤 긋기 Esc = 지움 전
- 771-2: 한 선 페이지(B05) 중간 노드 누름 = 그 노드에서 긋기 · 같은 선 다른 노드에 닿으면 그 사이 갈음(rejoinSpan · 거꾸로 됨 · 한 줄) · 같은 노드 · 안 닿고 끝냄 = 꼴 그대로 + 안내 · 되돌리기 한 걸음 · D03 는 중간 꺾임점 = 갈래 그대로
- 771-1: 긋기 Esc = 긋기 직전 꼴 · 지움(노드 · 토막 · 구역 · 전체 · 잇단 지움) 바로 뒤 긋기면 지움 전 꼴(D03 같은 부품)
- 시험: test_771_2_rejoin_span 새로 · test_626_3 의 lineLimitBlock 줄 문자열 대조를 새 꼴(branches 변수)로 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:48:42 +09:00
eomsangdonandClaude Opus 5.5 ee5125f012 knowledge(요구): 요구 614 원문 — 햄버거 메뉴 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 19:32:12 +09:00
eomsangdonandClaude Opus 5.5 b13eb584fb knowledge(요구): 요구 613 원문 — B05 예정노선 수정 취소 = 직전으로 · 중간 노드에서 다시 긋기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 19:31:02 +09:00
eomsangdonandClaude Opus 5.5 da408c942f knowledge(계획): 계획 이관 770 — 안정성평가 단면 면적 시험 낡음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 19:05:04 +09:00
eomsangdonandClaude Opus 5.5 bc8bc78fa2 test(770-1): 532-2 B06 면적 대조 = E03 분석 길(관 · 벽 측점 그려지는 설계선) · 검증_sub4_E 복사본
- 까닭: 768 새 자료 관 측점 340 · 342 · 1200 · 1201 m 에서 B06 [저장] 면적 = 관 트림 뒤 「그려지는 설계선」 기준(`structure.layouts` 폐회로 면적) · 시험만 원 설계선으로 재 어긋남(옛 검증_sub4 엔 관 측점 없음)
- E03 분석 길(`walls_by_station` · `design_with_walls`)로는 71 측점 모두 B06 과 같음(검증_sub4_E · 검증_sub6_E) — 코드 흠 아님 · 코드 안 고침
- 시험 = 옛 B 초기값 스냅샷 대신 E 복사본 + 분석 길 그대로 · 관 측점 있어야 함 확인

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:04:03 +09:00
eomsangdonandClaude Opus 5.5 0987a8e400 knowledge(계획): 계획 이관 761 ~ 769 — 서피스 확정 · 다듬기 · 추천 흐름 · 지형마다 설계 한 벌 · 필터 지움 · 프로젝트 새로 만듦
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 18:58:47 +09:00
eomsangdonandClaude Opus 5.5 208d755123 test(768-4): 검증 프로젝트 다시 만든 뒤 시험 상수 새 id 로 · 기슭막이 없는 검증_sub2 벽 시험 건너뜀
- 시험 20 파일의 옛 프로젝트 id → 같은 이름 새 id(B 검증_sub1~8 · D 가이드_예정노선 · 검증_sub1_D · E 가이드_안정성평가)
- test_536_3 벽 시험 둘 — 새 검증_sub2 = 구조물 0(옛 사용자 기슭막이 351 m 없음) · 벽 측점 없으면 건너뜀
- 고정 자료 golden_472 · helper_472_1_dump 는 옛 id 그대로(살아 있는 프로젝트 안 읽음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:54:09 +09:00
eomsangdonandClaude Opus 5.5 5852c93606 style(765-8): 체인 주석 줄 길이 맞춤
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:56:16 +09:00
eomsangdonandClaude Opus 5.5 57a6c0ee22 fix(765-8): 벌마다 평면노선 따로 — 받은 노선을 그 벌 지형으로 잘라 벌 예정노선으로 두고 따라감(요구 612)
- 벌 예정노선 terrain_route.csv(벌 몫) — 체인이 받은 노선을 그 지형으로 잘라 한 번 적음 · 초기 폴리라인(merged_follow) · 화면 예상노선 · 고칠 선 끝이 이것을 바탕으로(route_source_path · 없으면 공유 예상노선)
- 공유 예상노선 expected_route.csv = 설계 지형으로 자른 하나(추천 열쇠 그대로) · 고친 예정노선 expected_route_edit.csv 는 벌 몫(다른 벌에 안 번짐)
- 검증_sub5: 라이다 벌 1,352.9 m · 곡선 19(옛 값과 같음) · 수치지형도 벌 2,110.2 m · 곡선 28(예정노선 끝까지) · 칩 왕복 · 벌마다 [초기화] 값 같음 · 공유 예상노선 그대로
- 시험 test_765_design_sets — 벌 몫 가름에 벌 예정노선 · 고친 예정노선 · 벌 예정노선 자름 · 초기 폴리라인 바탕 둘 덧 · 지운 csf 필터 글자를 ptd · classification 으로(637 · 763_3 · 765)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:56:10 +09:00
eomsangdon 61c6f0e9df Merge remote-tracking branches 'origin/sub_laptop_3' and 'origin/sub_laptop_8' into main_laptop_1 2026-11-03 16:36:06 +09:00
eomsangdonandClaude Opus 5.5 1f990f2db6 knowledge(요구): 요구 612 원문 — 벌마다 평면노선 따로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 16:36:04 +09:00
eomsangdonandClaude Opus 5.5 11c75f022a refactor(767-1): 안 쓰는 지면 필터 · 서피스 지움 — 필터 넷(CSF · 격자 최저 · PMF · RANSAC) · 표현 셋(TIN + Taubin 평활 · NURBS · meshfree) · 수치지형도 비교 방식 다섯 · 안 부르는 길 정리
- 필터 = 업체 분류 · 점진 TIN · 둘의 다듬기 판만 · 분석 허용 목록 = Ground 등록부(available_filters) 한 벌
- 표현 = DTM · implicit 만 · 평활 = DTM 만 · config 기본 목록 · PRECOMPUTE · 설정 사전 맞춤(설정 지문 바뀜 — 768 에서 다시 지음)
- 등고선 = 격자 · 굳힌 격자만(삼각망 굽기 · 외곽 맞춤 · implicit RBF 폴백 걷음) · 표본기 · 경로 찾기 tin · nurbs · meshfree 갈래 걷음
- 수치지형도 = 라플라스만(TIN 둘 · TPS · ANUDEM형 · 다중해상도 걷음) · B04 단추 하나
- 안 부르는 길 — B04 JSON 점군 GET · ground-stats GET + TS · B07 옛 계획평면도(라이다) 짓기 · lidar_source · LIDAR_ID · plan_lidar 종류 이름 · SOURCE_FILTER_LABELS · MODEL_METHODS(py) · MESH_* 설정
- 옛 manifest 의 지운 갈래 줄은 DB 로 되살리지 않음 · 옛 csf 행은 미리보기 · 등고선 그대로 열림 · 지운 이름 요청 = 400
- 낡은 글 — Ground · Pipeline 머리 · WF1 「없으면 csf」 · B07 음영 기본 「csf」 → 빈 값 · 등고선 폴백 「csf」 → 404 · B04 3D 보기 처음값 · B05 Schema · B06 Prebuild · 씨닝 설명
- 시험 지움 2 — test_b04_contour_raster · test_b04_contour_equivalence(지운 삼각망 굽기 · 없는 프로젝트를 봐 건너뛰던 것)
- 시험 덜어냄 — test_b04_ground_filters CSF · 격자 최저 네 경우(지운 필터)
- 시험 고침 — 허용 목록 · 형 검사에 걸린 csf · tin · grid_min_z · meshfree · tin_sheet 견본을 ptd · implicit · laplace 로(54_22 · 238_1 · 443 · 342_2 · 346_3 · 351_1 · 442_2 · 271 + helper · 344_1 · 133_1 · 343_2 · 637 · 754_1 · 763_1 깨진 tin → 깨진 implicit · 763_3 · b05 둘 · b06 · sheet_surface TIN(도엽선) 경우 지움) · 이름만 바꿈(273_2 · 343_1 · 343_3 · 624_1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:35:12 +09:00
eomsangdonandClaude Opus 5.5 f7bd8dbf06 fix(769-1): 추천 4 갈래 = 계산 로직 결과 그대로 — 같은 쪽 곡선 합치기 · 측점 맞춤 뺌
- 요구 611: 추천노선 = 평면계획선을 긋는 가이드 선 · 합치기 · 스냅은 예정노선 수정 뒤 평면계획선에만
- _trace = 경로 탐색 → 곡선 넣기(curve_every_kink) → 큰 R(widen) 까지 · merge_same_side · settle 뺌
- 예상노선 추적(merged_follow) · 체인 · /route/plan · replan · follow 는 합치기 · 스냅 그대로
- Pool.snap · Pool.cross · build_pool snap · cross 인자 · 엔진 fit · route_earth · merge_delta 걷음(쓰는 곳 없음)
- ALGO_VERSION 9 → 10
- 낡은 시험 고침: test_419_1 · test_351_2 추천 갈래 측점 위 기대 뺌(추적만 측점 위 · 갈래는 안 맞춤 확인 추가) ·
  test_route_initial 영선 · test_351_2 비중 기울기 상한 기대 뺌(원지반 기울기 참고만 · 비중은 벌점만) ·
  test_224_4 build_pool 엿봄 인자

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:30:29 +09:00
eomsangdon db91aece63 Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-03 16:27:29 +09:00
eomsangdonandClaude Opus 5.5 e54ee5314a fix(765-3): 두 벌 = 같은 평면 노선 — 예상노선을 설계 지형으로 먼저 잘라 적고 다른 지형 벌도 그 노선으로 지음
- 검증_sub5 실측: 수치지형도 벌이 수치지형도로 자른 긴 노선(2110 m)으로 지어져 공유 예상노선(라이다로 자름 · 1377 m)과 어긋남 → 그 벌 [초기화] 뒤 초기 폴리라인이 예상노선(라이다)으로 다시 지어져 노선 1359.6 m · 종횡단 2110 m 로 갈림
- 예상노선(공유 · 추천 열쇠)은 하나 · 설계 지형으로 자름 → 옛 지형 칩처럼 평면 노선 같고 지형만 다름
- 다른 지형 벌 끝에 예상노선 지우던 길 걷음(먼저 적으니 필요 없음)
- 시험 test_765_design_sets 체인 차례에 예상노선 먼저 적기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:21:04 +09:00
eomsangdon 2937ba6208 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_5' into sub_laptop_3 2026-11-03 16:15:10 +09:00
eomsangdonandClaude Opus 5.5 968db57bdb knowledge(요구): 요구 611 원문 — 추천노선 = 가이드 선 · 합치기 · 스냅 안 함 · 예정노선 수정 뒤 평면계획선에만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 16:14:40 +09:00
eomsangdonandClaude Opus 5.5 bdaca728fe fix(765-6): 초기값 사본 · 복원에서 예상노선(공유) 뺌 — 수치지형도 벌 [초기화]가 라이다 예상노선을 덮던 것
- 검증_sub5 실측: 칩으로 수치지형도 벌 → [초기화] → 칩 라이다 뒤 예상노선이 수치지형도로 자른 것(643 점 · 2136.2 m)으로 남음 · 라이다로 자른 것 = 415 점 · 1377.4 m
- 예상노선은 지형마다 벌이 있어도 하나(공유 · 추천이 씀) — 초기값 사본에 안 넣고 복원이 안 건드림(옛 사본 안 것도 건너뜀)
- 시험 test_765_design_sets 에 덮지 않음 검사 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:12:24 +09:00
eomsangdonandClaude Opus 5.5 2a8f244ee0 knowledge(요구): 766 구멍 메움 전후 그림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 16:08:32 +09:00
eomsangdon 462a2f50fa Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_8' into main_laptop_1 2026-11-03 16:07:02 +09:00
eomsangdonandClaude Opus 5.5 8a50a155ab perf(765-2): 벌 꺼내 끼우기 자식 행을 표마다 한 번에 넣음 — 칩 5.5~8 s → 약 2 s
- 검증_sub5 실측: _load_db 3.1~5.2 s(원격 DB 에 횡단 · 정점 줄마다 INSERT) → 0.5 s
- test_637_lidar 줄 길이 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:06:23 +09:00
eomsangdonandClaude Opus 5.5 fe6f67d123 fix(765 이음매): LAS 나중 올리기 옛 필터 지우기가 새로 지은 다듬기 모델까지 지우던 것 — 지은 필터가 옛 이름을 앞머리로 품으면 남김
- 766-1(기본 = 업체 분류 + 다듬기 · classification_refine) 과 637-3 drop_stale_filters 가 만나 classification 을 지우며 dtm_classification_refine.npz · implicit_classification_refine*.npz 까지 지움(라이다 짓기 직후 모델 없음)
- test_637_lidar B 뿌리 짓기: 기대 필터를 다듬기 이름으로(옛 csf · ptd 기본 아님) · 모델 npz 만 봄 · 긴 이름 지은 필터 지킴 시험 하나 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:04:17 +09:00
eomsangdonandClaude Opus 5.5 08281bd712 feat(766-2): 외곽 안 구멍을 둘레 지면에서 매끈하게 메움 — 다듬기 판 외곽 = 바탕 필터 외곽 · 쌍조화 메움 · implicit 길잡이 · 품질 지표 「메운 넓이」
- 다듬기 판(classification_refine · ptd_refine)은 바탕 필터 지면점으로 외곽을 그림 — 다듬기가 지운 자리 = 외곽 안 구멍 · 외곽 자체는 안 넓힘(엔진이 바탕 마스크를 모델 짓기에 넘김 · 지면점 저장본은 안 지음)
- dtm 격자: 땅 칸에서 틈 닫기(3 m) 넘게 먼 칸 + 그 둘레 빈 칸을 쌍조화(scikit-image inpaint_biharmonic · 이미 씀)로 메움 · 작은 틈은 지금처럼 가까운 칸 값
- 라플라스(막 펴기)는 버림 — 홀로 남은 땅 점마다 고깔이 생겨 용화 새 자리 튐 8.2 %(쌍조화 6.4 %)
- implicit: 메운 dtm 칸을 3 m 간격 길잡이 중심점으로 더함 — 구멍 칸 튐 3.1 → 0.8 %(dtm 과 같은 면) · 중심점 뽑기 칸은 자기 외곽 넓이 그대로
- 품질 지표 ⑤ 메운 넓이 m² · % — dtm `filled_mask` · B04 값 목록 한 줄 · QUALITY_VERSION 2
- 모델 지문에 hole_fill 더함 — 다음 전처리 때 모델을 다시 지음
- 지킴 시험 test_766_2_hole_fill — 평면 · 곡면 비탈 그대로 이음 · 작은 틈 안 건드림 · 바탕 외곽 그대로 · dtm · implicit 구멍 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:03:51 +09:00
eomsangdon 5523522df4 Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-11-03 16:01:53 +09:00
eomsangdonandClaude Opus 5.5 c89c9223e3 fix(765-6): 초기값 사본 · 복원의 캐시 규칙을 B05 비용면에도 — 사본에 안 넣고 복원이 안 지움
- 검증_sub5 실측: 초기값 사본 B05_Profile__route 4.4~5.6 MB 의 대부분이 추천 비용면(cost_surface_*) — 배수 캐시와 같은 공유 캐시
- 트리마다 공유 캐시 앞머리(_TREE_CACHES) · 뜨기 = 뺌 · 복원 = 설계 파일만 갈음(예상노선은 그대로 뜸)
- 시험 test_765_design_sets 하나 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:00:07 +09:00
eomsangdon 91985adf29 Merge remote-tracking branches 'origin/sub_laptop_3' and 'origin/sub_laptop_5' into main_laptop_1 2026-11-03 15:59:37 +09:00
eomsangdon 4781a14768 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-03 15:59:34 +09:00
eomsangdonandClaude Opus 5.5 c29d72639d knowledge(요구): 분석 608 — 안 쓰는 필터 넷 · 서피스 셋 · 수치지형도 비교 다섯 · 안 부름 여섯 지움 검토 · 약 1,800 줄
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 15:54:40 +09:00
eomsangdonandClaude Opus 5.5 9b12d4ff5d feat(765-6): 초기값 사본에서 배수 공유 캐시 뺌 · 복원은 노선 몫만 갈음(캐시 제자리) · 복제가 설계 벌 묶음 안 지표면 id 고침
- common_util_initial_snapshot: 배수 사본 = 노선 몫만(하강장 · 도엽 좌표 · 강우 뺌 · 약 105 → 20 MB) · 복원은 폴더 통째 지우지 않음 · 캐시 든 옛 사본의 캐시 칸은 건너뜀
- common_util_project_copy._fix_files(통째 복제 · --replace 같은 길): design_sets/<지형>/ 의 db.json · 그 벌 초기값 · B06 표본 설정 · meta.json 지표면 id → 새 id · 노선 id 는 꺼낼 때 늘 새로 받아 안 고침 · 코리도 안 건드림
- 시험 test_765_design_sets 에 둘 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:52:46 +09:00
eomsangdonandClaude Opus 5.5 0dbe4d5f31 feat(765-4): 지형 칩 = 벌 맞바꿈 · LAS 나중 올리기 뒤 = 확인창 → 전처리 다시(두 벌 · B05~B09 초기화)
- B05_Profile_Router_TerrainSwitch: 칩 = swap_design_set(떠난 벌 [저장] 고침 살림 · 묶음 없음 · 묵음 = 한 번 지음 · 실패 = 통째 되돌림) · force = switch_terrain → run_surface_change_chain(765-5)
- 확인창 글: 바꿈 = 미리 지어 둔 그 지형 설계로 바꿔 끼움 · 전처리 다시 = 두 벌 새로 · B05~B09 초기화 · LAS 올림 끝 = 늘 전처리 다시
- 시험: test_626_1 칩 시험을 맞바꿈 · 전처리 다시 기준으로(옛 노선 그대로 다시 셈 길 없음) · test_637 force 시험 · 화면 줄 · test_763_3 서피스 바꾸는 자리 목록에 지형 칩

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:50:38 +09:00
eomsangdonandClaude Opus 5.5 7ada1a643c knowledge(요구): 요구 610 원문 — 지울 범위 B · D · E 모두 · OWNERS 새 id 는 브레인이 고침
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 15:49:54 +09:00
eomsangdonandClaude Opus 5.5 d9e8dc4177 fix(764-1): 추천 측점 맞춤 덜 나쁜 자리에 원지반 기울기 늘어남 · 옮긴 거리 따짐
- 요구 606-2 · 분석 761-6 — 설계 지형에서 측점 맞춤이 후보 7/8 을 비탈로 옮겨 추천 셋이 같은 선
- 덜 나쁜 자리 순위 = 계류 나란히 → 기울기 늘어남 → 옮긴 거리 · 꼴(합친 꼴)을 가로질러 견줌
- 크게 망침(기울기 넘친 만큼 +5 %p 초과 · 원래 선에서 10 m 초과)은 그것 없이 다 놓는 길이 없을 때만 · 그때 가장 덜 망치는 자리(곡선 가운데 = 측점 유지)
- 놓을 자리 풀이 기억(층 · 다시 셈 공통) · ALGO_VERSION 9(옛 저장본 안 꺼냄)
- 지킴 시험 test_764_1_snap_grade.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:48:26 +09:00
eomsangdonandClaude Opus 5.5 a44fd98adf feat(765-3): 전처리 체인 = 지형마다 두 벌 — 다른 지형 먼저 짓고 묶음으로 떠 둠 → 설계 지형 → 추천 미리 셈 한 번 · 배수 늘 다시 셈 · 비우기에 묶음 · 죽은 run_redesign_chain 걷음
- run_preprocess_chain: WF1 자동 확정 · 서피스 바꿈 체인이 부름 · 다른 지형 없음 · 실패 = 한 벌 + meta.json 까닭 · 예상노선(공유)은 설계 지형 벌이 씀
- 자동 체인 배수 refresh=True — 저장분이 노선 바뀐 줄 모르던 틈 막음
- purge_output_folders 가 design_sets 도 지움
- run_redesign_chain(앱에서 부르는 곳 0) 걷음 — 시험 정리: test_342_2 의 그 함수 시험 셋 지움(함수 없음) · test_763_3 · test_351_1 은 두 벌 체인으로 · test_74 · test_user_values_survive 의 이월 차례 검사는 살아 있는 자리(B05_Profile_Route_Save)로 옮김
- 시험 test_765_design_sets 에 체인 차례 · 한 벌 · 다른 벌 실패 · 비우기 넷 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:47:20 +09:00
eomsangdonandClaude Opus 5.5 a64d0fc57e feat(765-2): 지형마다 설계 한 벌 묶음 모듈 — 떠 두기 · 꺼내 끼우기(새 노선 id · 자식 다시 잇기 · 코리도 이름) · 지문 · 맞바꿈(실패 = 통째 되돌림)
- common_util_design_sets: 활성 벌 = 제자리 · 쉬는 벌 = design_sets/<las|sheet>/(db.json · files/ · initial_snapshot/ · meta.json)
- 벌 몫 = B05 노선 정본 · 코리도 · B06~B09 · 배수 노선 몫 · 초기값 · 공유 캐시(비용면 · 예정노선 · 하강장 · 도엽 좌표 · 강우)는 안 옮김 · 안 지움
- 옮김 = 같은 볼륨 이름 바꿈 · 단계 2 이후 행 · projects.status 함께
- 묶음 없음 · 지문 어긋남 = 그 자리에서 자동 설계 체인 한 번(inner = 마커 · 미리 셈은 부르는 쪽)
- 시험 test_765_design_sets 6 (왕복 값 같음 · 새 id · 묵은 벌 · 실패 되돌림)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:42:28 +09:00
eomsangdonandClaude Opus 5.5 8b49d9cd92 knowledge(요구): 요구 609 원문 — 업무 끝난 뒤 프로젝트 전체 지우고 같은 이름으로 검증 프로젝트 다시 만듦
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 15:41:00 +09:00
eomsangdonandClaude Opus 5.5 2d69a494bd knowledge(요구): 요구 608 원문 — 초기값 사본 캐시 · 응답 속도 물음 · 안 쓰는 필터 · 서피스 지움 검토
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 15:36:49 +09:00
eomsangdon dc16c500d0 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_8' into sub_laptop_5 2026-11-03 15:30:40 +09:00
eomsangdonandClaude Opus 5.5 7876465f8a knowledge(요구): 분석 765 — 지형마다 설계 한 벌 · 한 벌 약 20 MB · 약 30 s · 활성만 DB · 다른 벌 묶음 · 칩 = 맞바꿈
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 15:30:08 +09:00
eomsangdonandClaude Opus 5.5 579e337892 feat(766-1): 다듬기 흐름을 기본으로 — 스위치 SURFACE_REFINE_FLOW 걷음 · 자동 전처리 = 업체 분류 + 다듬기(분류 있음) · 점진 TIN + 다듬기(늘) · 확정 기본 ptd_refine
- resolve_auto_source_filters 가 늘 `_refine` 판을 돌려줌 — 바탕 마스크(업체 분류 · 점진 TIN)는 다듬기 입력으로 그대로 저장(엔진 길 그대로)
- SURFACE_CONFIRM_DEFAULT_FILTER 기본 = ptd_refine · WF1 자동 확정은 엔진이 고른 첫 필터 그대로
- B04 드롭다운 갈래 넷 그대로(관리자 비교용)
- 지킴 시험 고침: test_640_lidar_default · test_b04_ground_filters 의 자동 목록 기대값을 다듬기 판으로 · test_762_1_refine 의 스위치 시험은 스위치가 없어져 「기본 흐름」 시험으로 바꿈

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:20:19 +09:00
eomsangdonandClaude Opus 5.5 c82f301e2b knowledge(요구): 요구 607 원문 — 다듬기 씀 · 구멍은 자연스럽게 메움
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 15:14:51 +09:00
eomsangdonandClaude Opus 5.5 8811fbf85a knowledge(요구): 요구 606 원문 — 지형마다 설계 한 벌 미리 · 측점 맞춤 옮길 자리에 기울기 따짐
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 15:13:40 +09:00
eomsangdonandClaude Opus 5.5 136d1e014d knowledge(요구): 분석 761-6 추천 셋 같은 선(측점 맞춤 덜 나쁜 자리) · 762 점 필터 다듬기 시범 보고 · 그림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 14:53:31 +09:00
eomsangdon e9435881d7 Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_2' and 'origin/sub_laptop_8' into main_laptop_1 2026-11-03 14:51:29 +09:00
eomsangdonandClaude Opus 5.5 11c8bd4aa5 fix(762-2): B04 품질 지표 값 목록 — 땅 점 빈 곳 · 그중 급경사 두 줄 · 필터 차는 % 만(짝 이름은 마우스 올림 글) · 값이 두 줄로 깨지던 것 고침
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 14:15:12 +09:00
eomsangdonandClaude Opus 5.5 e2b7133225 feat(763-4): 추천 미리 셈 끊기면 이어 셈 · 화면 진행 n/m · 저장본 없으면 [지금 셈]
- 진행 기록 `B05_Profile/route_recommend_job.json`(저장본 폴더 밖 — 저장본 glob 에 안 섞임) · `note_job` 재사용 · 갈래마다 갱신 · 끝 = running 거짓
- 진행 묻기 = 이 서버 메모리 먼저 · 없으면 기록(10 분 안 = 다른 서버가 셈 · 넘음 = 끊김 `interrupted`)
- 추천 요청 409 + 「끊김」 기록 = 이어 셈 예약 한 번(기록을 곧바로 「도는 중」으로 · 뒤 요청은 안 겹침)
- 미리 셈 한 갈래 409 = 그 갈래만 건너뜀(옛: 전부 멈춤)
- 409 글 = 「추천 저장본 없음 — 지표면이 바뀌어 다시 셈해야 함」(없는 [재계산] 단추 안내 걷음)
- 화면(RecPreview) — 셈 중이면 줄 마우스 글 「추천 셈 중 n/m」 · 알림 한 번 · 3 초마다 다시 물어 끝나면 그림 · 아니면 확인창 [지금 셈](그 갈래만 recompute) · 지형 바뀌면 받아 둔 선 비움
- 시험 309_3 · 364_4 · 509_5 · 626_1 을 새 글 · 진행 꼴 · [지금 셈]에 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 14:01:28 +09:00
eomsangdonandClaude Opus 5.5 6d0b50aee6 feat(762-2): 서피스 품질 지표 넷 — 튐 넓이 % · 1 m 등고선 꺾임 /km · 땅 점 빈 곳 % · 필터 차 % · B04 모델마다 표시
- 새 Engine_Quality — 599 정의 그대로(5 × 5 중간값 0.5 m · 2 m 다시 찍어 30° · 2 m 안 지면점 없음 · 짝 필터 |차| > 0.5 m)
- 파이프라인이 dtm · implicit 을 지을 때 셈해 models/quality_{필터}_{표현}.json 에 저장 · 모델 · 짝 모델 지문이 다를 때만 다시 셈
- GET /surface/models/{id}/quality · B04 지표면 분석 칸에 값 목록(부품 kv) · 문턱 판정 없음(사용자 협의)
- B04 필터 드롭다운 · 이름을 새 UI_Filter 로 옮김(UI_Page 679 → 661 줄) · 업체 분류 + 다듬기 · 점진 TIN + 다듬기
- 지킴 시험 test_762_1_refine — 덤불 혹 지움 · 능선 안 깎음 · 스위치 끔 = 지금 길 · 튐 지표

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:46:51 +09:00
eomsangdonandClaude Opus 5.5 c0f4c766b6 feat(762-1): 지면점 다듬기(SMRF 꼴) · 고를 갈래 업체 분류 + 다듬기 · 점진 TIN + 다듬기 · 제안 흐름 스위치(기본 끔)
- 새 Engine_Filter_Refine — 1 m 칸 최저 · 낮은 잡점(뒤집어 열림) · 창 키움 열림(최대 18 m · 기울기 0.3) · 걸린 칸 빼고 임시 지면 다시 그림 · 점마다 0.5 m + 1.25 × 기울기
- 필터 이름 classification_refine · ptd_refine — Ground _FILTERS · Schema · sampler SurfaceFilter 에 더함 · 엔진은 바탕 마스크 저장본을 다듬음(점진 TIN 두 번 안 돌림)
- 스위치 SURFACE_REFINE_FLOW(프로세스 환경 변수 · 기본 끔) — 켜면 자동 전처리 · 확정 기본 = 다듬기 판 · 끄면 지금 길 그대로
- 기울기 0.3 = 용화 훑기에서 고름(PDAL 0.15 는 급경사 능선을 더 깎음 · 0.45 · 0.6 은 덤불 혹 더 남김)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:46:33 +09:00
eomsangdonandClaude Opus 5.5 cf09d53924 fix(763-3): B04 분석 · 조합 짓기 = 보기만 · 다른 서피스 [확정] = 업로드와 같은 전처리 체인 · 체인 실패에도 미리 셈
- B04 [분석](조합 짓기) — 단계 시작 · 확정 해제 · 실패 단계 기록 걷음 · 1 단계 값(las_selection 포함) 그대로 · 미리 셈 없음 · 진행 글 = 확정 있으면 「확정 그대로」
- 분석 결과 DB 고쳐 쓰기가 확정 모델을 COMPLETE 로 내리던 것 — CONFIRMED 그대로(`_upsert_surface_model`) · 안 쓰는 `clear_confirmed_surface_models` 지움
- B04 [확정] — 같은 서피스(필터 · 표현 · 평활) = 등고선 간격만 · 다른 서피스 = `run_surface_change_chain`(새 파일): 초기값 스냅샷 버림 · B05~B09 지움(같은 지형 재업로드 길 keep_preprocess) · 단계 2 이후 처음 → 자동 설계 체인
- 자동 · 재확정 체인 미리 셈 = finally(실패 · 건너뜀 길에도) · B05 LAS 나중 올리기 끝 미리 셈 예약
- 추천 [적용](as_initial) = 선만 · 설계 지형 그대로(요구 605-2 · 브레인 승인) · 지형 칩은 사용자 답 기다림(지금대로)
- 지킴 시험 test_763_3 — B05~B09 파이썬에서 확정 서피스 쓰는 부름은 지형 칩 · 라이다 적어 둠 둘만
- 시험 309_3 · 351_1 · 509_5 를 새 길에 맞춤(미리 셈 부르는 곳 둘 더 · finally · 추천 [적용] 지형 안 넘김)
- 재확정 체인(`run_redesign_chain`) 부르는 곳 0 — 지움은 따로(시험 342_2 셋 함께)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:45:30 +09:00
eomsangdonandClaude Opus 5.5 a2b2ae57b2 fix(761-5): 복제 · 바꿔 넣기 뒤 노선 · 지표면 id 박힌 storage 파일 새 id 로
- common_util_project_copy.rekey_id_files: 코리도 이름 corridor_{노선 id:04d} → 새 노선 id(겹침 대비 임시 이름 거침) · 짝 없는 옛 코리도 지움 · B06_Section/sampling.json surface_model_id → 새 지표면 id
- _insert_rows · _insert_full 이 (지표면 · 노선) id 짝을 함께 돌려줌 · full 복제 · --replace · --storage-only 셋 다 _fix_files 에서 rekey
- id_maps_by_order: 이미 복제된 두 프로젝트의 id 짝(차례 · 행 수 다르면 ValueError) — storage-only 가 씀
- storage 경로 함수 전수: id 박힌 이름 = 코리도뿐(B08 이정표 그림 이름 = 거래처 상자 id · 추천 저장본 = 해시) · 내용 = sampling.json 지표면 id(초기값 스냅샷 지표면 id 는 종전대로)
- 지킴 시험 test_761_2: 노선 id 짝 단정 · rekey(이름 겹침 · 짝 없음 지움 · sampling)
- 바꿔 넣은 B 13 개 고침(tmp 스크립트 · 차례 짝 + 내용 칸 대조): 코리도 셋씩 새 id · corridor_0370 지움 · sampling 911 → 각 새 id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:38:20 +09:00
eomsangdon 47f9d90f97 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_1 2026-11-03 13:28:10 +09:00
eomsangdonandClaude Opus 5.5 6af3f990ff fix(763-2): 같은 서피스 = 파일 내용 지문 — 비용면 · 계류 · 초기 종단 · 초기 종단 목표 = 고른 지형
- `file_digest()`(derived_store) — blake2b 내용 지문 · 한 프로세스 안 경로 · 시각 · 크기로 기억
- 비용면 서명 · 계류 지문 · 초기 종단 `stat_marks` = 내용 지문(옮겨 심기 · 동기화 · 다시 짓기로 시각만 바뀌면 같은 열쇠)
- 초기 종단 표식 = 확정 지형이 읽는 파일만(`_source_npz_paths` · `*_{필터}*.npz` glob 걷음)
- `balanced_profile_of` 에 selection — 추천 초기 종단 목표 계획고 = 지반선과 같은 고른 지형(3h)
- 열쇠 꼴이 바뀌어 기존 추천 · 초기 종단 저장본은 다시 셈(763-5 몫)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:23:31 +09:00
eomsangdon 6e30772e43 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-03 13:18:30 +09:00
eomsangdon 0f810fe9bf Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-03 13:18:28 +09:00
eomsangdonandClaude Opus 5.5 80c2aabfcf fix(763-1): 추천노선 = 확정 지표면에서만 — 확정 전 409 · 비용면 = 굳힌 격자 마디 · 조용한 DTM 대체 걷음
- `confirmed_surface_params()` — stage 1 COMPLETE · params 에 source_filter 있을 때만 · 아니면 None
- compute_initial = 확정 전 409 「지표면 확정 전 — B04 [확정] 뒤 추천이 생김」 · 미리 셈 지형 = [](건너뜀 로그)
- Solver implicit 갈래 = `implicit_grid_npz` 마디 그대로(RBF 다시 안 셈 · 짓기 16.2 s → 0.02 s · 유효 칸 최대차 0.00003 m)
- 모델 파일 없음 · 깨짐 = 까닭 그대로 FileNotFoundError · ValueError(409) · 서명에 굳힌 격자(서명 전에 지음)
- 시험 224_3 · 351_1 · 509_5 의 확정 갈아 끼우기를 새 함수로 옮김(라우터가 새 함수를 부름)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 13:17:31 +09:00
eomsangdonandClaude Opus 5.5 106d179be2 knowledge(요구): 요구 605 원문 — B04 = 관리자 특수 경우용 · 서피스 바꾸면 전처리 다시 · B05~B09 초기화 · B05~B09 확정은 서피스 안 바꿈
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 13:17:20 +09:00
eomsangdon 92f74671ad Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_1' into sub_laptop_2 2026-11-03 13:02:58 +09:00
eomsangdonandClaude Opus 5.5 5043f29a62 knowledge(요구): 분석 603 — 전처리 로직 점검 · 확정 없을 때 기본값 셈 · B04 조합 짓기가 확정 풂 · 미리 셈 거는 곳 셋뿐 · 끊김 이어 셈 없음 · 고칠 걸음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 13:02:16 +09:00
eomsangdonandClaude Opus 5.5 4db52b5504 feat(761-2): 복제 도구 자리 바꿔 넣기 --replace — 대상 id · 이름 · 주인 · 참여자 · 종류 유지
- common_util_project_copy_replace: 대상 옛 행(복제 표 목록) 지우고 원본 행으로 채움 · projects 는 유지 칸 밖만 원본 값 · storage 를 원본과 똑같이(robocopy /MIR) · 넣기 전 대상 행 덤프(--backup-dir)
- common_util_project_copy: 행 넣기를 _insert_rows 로 떼어 full 복제 · 바꿔 넣기가 함께 씀 · robocopy 출력 글자 깨짐 무시
- 지킴 시험 test_761_2_clone_replace(유지 칸 · 지우는 표 = 복제 표 · id 다시 잇기)
- test_532_1 낡은 단정 고침: 지표면 지워진 옛 노선의 NULL(ON DELETE SET NULL)을 새 id 밖 값으로 잘못 셈
- 실제 한 번: 가이드 → 임시 복제 → 그 자리에 --replace → 행 수 · 파일 같음 · 추천 4 꺼내짐 → 앱 영구 삭제 길로 지움

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:57:40 +09:00
eomsangdonandClaude Opus 5.5 a16fb1c039 knowledge(요구): 요구 604 원문 — sub8 에 점 필터 · 서피스 제안 코드 시범 · 프로젝트 하나로 보여 줌
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 12:56:32 +09:00
eomsangdonandClaude Opus 5.5 28f222a3dd knowledge(요구): 요구 603 원문 — 가이드 확정 업체 분류 · implicit · B 프로젝트 모두 가이드 복제 · 전처리 로직 점검
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 12:44:31 +09:00
eomsangdonandClaude Opus 5.5 56b05665f6 knowledge(요구): 분석 602 — 프로젝트별 서피스 출처 · 추천 저장본 안 꺼내짐 셋 · 까닭 = 파일 시각 열쇠 · 확정 풀림 · 미리 셈 끊김
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 12:33:49 +09:00
eomsangdonandClaude Opus 5.5 91cd80378a knowledge(요구): 분석 601-1 — 라이다 지면 · 식생 가름 외부 조사 · 품질 확인 지표 · SMRF 꼴 다듬기 1 순위
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 12:24:09 +09:00
eomsangdonandClaude Opus 5.5 983551381b knowledge(요구): 분석 601-2 — 라이다 점 필터 · 서피스 코드 목록 · 안 부름 6 · 정리 후보
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 12:20:52 +09:00
eomsangdonandClaude Opus 5.5 c482d0cc2c knowledge(요구): 요구 602 원문 — 프로젝트별 서피스 필터 · 생성법 · B05 추천노선 보기 데이터 없음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 12:16:07 +09:00
eomsangdonandClaude Opus 5.5 f40fa2b767 knowledge(요구): 요구 601 원문 — 라이다 지면 · 식생 가름 외부 조사 · 점 필터 · 서피스 코드 정리 표
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 12:06:36 +09:00
eomsangdonandClaude Opus 5.5 9f3b3d9ac1 knowledge(계획): 계획 이관 758 ~ 760 — 따라가기 곡선 최적 · 사용자 셋 지표면 · 암상 지질도
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 08:39:38 +09:00
eomsangdon 8ed718ed75 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-03 08:39:04 +09:00
eomsangdonandClaude Opus 5.5 c187f30e8e feat(758-1): 예정노선 따라가기에도 토공 최소 곡선 최적 — 큰 R · 같은 쪽 곡선 합치기 · 직접 잼 측점 맞춤
- 새 `B05_Profile_Engine_RouteInitial_Follow.merged_follow` — 예정노선 폴리라인 → 큰 R(widen) → 꺾임만 곡선 → 같은 쪽 합치기(merge_same_side · 노선 횡단 토공 +10 % 멈춤) → 곡선 MC 일반측점 맞춤 · 잼 = 토공 최소와 같은 Guard(지형 · 계류 격자) · 같은 입력 기억
- 한 함수 — 체인 처음 · /route/plan · /route/replan(`initial_land` · 초기선 낡을 때만 지형 읽음) · /route/follow(`follow_land`) · 추천 「예상노선 추적」(follow_line · 묶음 격자 재사용)
- 지형 못 읽으면 옛 길(순수 기하) · 기존 확정 노선 · 초기선 · 저장본은 안 바꿈(다음 체인 · 끌 때부터)
- 화면 — 따라가기 뒤 지금 추천 = 예상노선 추적(「토공 최소」 꼬리표 남던 흠) · 걸음 사진에 실려 되돌리기 짝
- 엔진 합치기 잣대 셈(cross_total · cross_delta)은 새 파일 한 벌로 옮김(엔진 줄 줄어듦)
- 시험: test_758_1 새 지킴 · test_383_1 은 체인 초기선도 같은 지형을 넘기게 고침(추천 추적 = 곡선 최적이라 순수 기하와 달라짐) · test_626_3 은 onFollow 새 꼴로 글 대조 고침
- 검증_sub1: 곡선 25 → 16 · 측점 16/16 · 예정노선과 최대 10.6 m · 끌 때 1.4~1.9 s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 08:38:17 +09:00
eomsangdonandClaude Opus 5.5 18e080f75e knowledge(요구): 분석 599 — PTD · CSF 서피스 품질 비교 · 튐 까닭 = 남은 덤불점 · 지면점 열림 거름 제안
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 08:38:15 +09:00
eomsangdon 9feb507a71 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-03 08:33:30 +09:00
eomsangdonandClaude Opus 5.5 f6cfaf446c feat(760-1): 암상 지질도 겹 · 노선 따라 바위 종류 구간 · 밖 겹 출처 줄
- 서버: 지오빅데이터(KIGAM) 수치지질도 1:50,000 — WFS 암상 면(깨진 한글 칸은 기호별 점 조회로 채움) · 받은 범위 ∪ 노선으로 자름 · 저장본 · 노선 따라 1 m 표본 구간 · 키는 서버만(.env GEOBIGDATA_API_KEY · 쿼리 key)
- 그림 조각 대리에 geology(키 쿼리 이름 칸 key_param · 옛 겹 serviceKey 그대로)
- 화면(D03 · B05): 「제한·재해」에 「암상 지질도」 — 그림 조각 + 갈래 색 면 · 「기호 · 암상」 글 · 범례 · 노선 구간 표(참고 — 토사 · 암 판정 아님)
- 켠 밖 겹마다 범례 맨 아래 출처 줄(브이월드 · 국토지리정보원 · 산림청 · 환경부 · 생활안전지도 공공누리 · 한국지질자원연구원 CC BY-NC)
- 범례 칸 높이 한도 · 밀기(낮은 화면)
- 시험: test_760_1 새로 · test_634_2 · test_553_4 겹 차례 표에 geology 한 줄 더함(새 겹 · 낡은 표)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 08:32:53 +09:00
eomsangdonandClaude Opus 5.5 9685ba70d4 knowledge(요구): 요구 600 — 599 조사만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 08:11:06 +09:00
eomsangdonandClaude Opus 5.5 080938f562 knowledge(요구): 요구 599 — 점진 TIN 튀는 구간 · CSF 와 서피스 품질로 비교 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 08:10:37 +09:00
eomsangdonandClaude Opus 5.5 2efea6da90 knowledge(요구): 요구 598 — 예정노선에도 토공 최소 곡선 최적 · 지표면 새로 지어 옮김 · 지질도 개발용 · 출처 표기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 08:04:00 +09:00
eomsangdonandClaude Opus 5.5 f28268f050 knowledge(요구): 분석 597 — B05 둘째 단계 안 빠짐 · 지표면 옮겨 심기 · 지오빅데이터 접속 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 06:55:11 +09:00
eomsangdonandClaude Opus 5.5 018297a34a chore(설정): 지오빅데이터(KIGAM) 오픈API 키 .env GEOBIGDATA_API_KEY 로 · 요구 597
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 06:43:40 +09:00
eomsangdonandClaude Opus 5.5 06eaaaafe7 knowledge(요구): 요구 597 — B05 둘째 단계 확인 · 지표면 채우기 확인 · 지오빅데이터 키
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 06:43:40 +09:00
eomsangdonandClaude Opus 5.5 cd9e209174 knowledge(요구): 요구 596 — 지질도 위치 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 06:36:22 +09:00
eomsangdonandClaude Opus 5.5 b9a486f29a knowledge(계획): 계획 이관 751 — 원화 표기 · 셈 끝수 품셈 1-2-2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 06:19:28 +09:00
eomsangdonandClaude Opus 5.5 edb2596c63 test(751-3): 받은 753-2 시험(이어 붙인 모델)에 B09 rounddown 같은 식 — 모델 소계 1 원 버림
- test_750_2 753-2 시험이 Cover_Model 을 import 빼고 이어 붙여 돌림 → 751-3 이 더한 `rounddown` 가져옴이 빠져 ReferenceError · 같은 식 한 줄 더함(704_3 · 717_2b 와 같음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 06:17:52 +09:00
eomsangdon 0ef349463d Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_3 2026-11-03 06:16:21 +09:00
eomsangdon 037b9de76d Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-11-03 06:16:18 +09:00
eomsangdonandClaude Opus 5.5 f4be6782a5 feat(751-3): M01 · M02 셈 끝수 = 품셈 1-2-2 버림 — 줄 0.1 원 · 비목 소계 1 원 · 계 = 셋 합 · B09 와 같은 값
- 끝수 한 곳 `master_cut.py`(B09 `rounddown` 재사용) — 엔진 답에 입힘: 줄 비목마다 0.1 원 버림 · 하위 로직 줄 = 하위 끝수 계 × 수량 · 덧줄 = 버린 줄 합으로 다시 셈 · 비목 합 1 원 버림 · 중간 칸도 버린 값
- 쓰는 곳: M01 시험 계산(`check_master.calc`) · 읽는 식(`master_text.lines`) · 옛 조합 미리보기(`master_combo`) · M02 `run_line`(단가산출 · 일위대가 · 펼침) · 별도계상 뺀 기준 · 켜쌓기율 곱 뒤 · 일위대가 반영 · 반영합 · 비율 줄 · 사전조합 안 줄
- 일위대가 반영 TS 짝(`Labor_Calc.ts`) 같은 끝수 · 거울 헬퍼가 상대 경로 .ts 가져옴
- B09 = `run_line(raw=True)` 엔진 날 값(제 단가로 다시 버림 · % 덧줄 율 비 그대로) — 검증_sub3 B09 247 칸 전후 같음
- `master_text` 꼴 셋 = `common_util_money` 로 갈음(줄 금액 `fmt_line_money` · 식 안 금액 = 버린 0.1 원)
- 화면: 상세 줄 금액 · 시험 계산 줄 · 중간 돈 = `formatLineMoney` · 일위대가 반영 칸 0.1 원 · 상세 바닥 · 박스 · 인력재료경비 비목 소계 = 줄 합 1 원 버림
- 테스트1 네 보기 · 서고 목록 = 124,419(옛 124,420 / 124,419.85) · 재료 줄 11,694.6 + 23,425.6 + 1,404.8 = 소계 36,525
- 새 지킴 시험 test_751_3_money_cut
- 옛 날 금액(끝수 없음)을 대조하던 시험 24 파일 고침 — 까닭 = 금액 규칙이 줄 0.1 원 · 소계 1 원 버림으로 바뀜(고정 값 → 새 값 · 줄 합 = 계 → 줄 합 아래 비목마다 1 원 안 · 곱 비 = 끝수 폭 안) · 실무 xlsx 영월 엑셀 같음 22 → 23 · 이어 붙이는 TS 시험(704_3 · 717_2b)에 `rounddown` 같은 식

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 06:16:01 +09:00
eomsangdonandClaude Opus 5.5 486e9887eb knowledge(요구): 요구 595 — sub3 끝나면 전역 깃 싱크 · 랩탑 메인 4173 다시 짓기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 06:03:02 +09:00
eomsangdonandClaude Opus 5.5 d5196d64d1 knowledge(요구): 요구 594 — 최근 일주일 지우기 대상 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 05:48:03 +09:00
eomsangdonandClaude Opus 5.5 adf14f0ebc knowledge(요구): 요구 593 — tmp 폴더는 마음대로 · 정리 길
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 05:41:48 +09:00
eomsangdonandClaude Opus 5.5 75a902a7e1 knowledge(요구): 요구 592 — 작업 창 bash 허락 막힘 대책
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 05:34:27 +09:00
eomsangdonandClaude Opus 5.5 e4d8c958d1 knowledge(요구): 요구 591 — sub3 허락 창 사용자가 승인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 05:33:35 +09:00
eomsangdonandClaude Opus 5.5 04365e28d3 knowledge(요구): 요구 590 — 감시 고리 점검 · sub3 물음 진짜
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-03 05:31:53 +09:00
eomsangdon 2d3a713512 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-02 23:35:04 +09:00
eomsangdonandClaude Sonnet 5.5 94b6869b99 refactor(751-2): calc_sheet 직접 꼴 공용 꼴로 · test_719_4 공용 꼴 파일 더함
- calc_sheet.ts won = toLocaleString 직접 꼴 → formatMoney(0 자리) · formatLineMoney(1 자리) · 보이는 값 같음
- test_719_4 낡은 시험: node 로 붙이는 TS 목록에 common_util_money.ts 더함(751-1 뒤 formatNumber 미정의 ReferenceError) · 재는 것은 그대로

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 23:34:37 +09:00
eomsangdonandClaude Opus 5.5 4286b744af knowledge(계획): 계획 이관 754 — B04 등고선 외곽 · 횡단배수 첫 열림 빈 폼
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 23:33:16 +09:00
eomsangdon d478761d19 Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-11-02 23:32:40 +09:00
eomsangdonandClaude Opus 5.5 79c53be5d3 fix(754-2): B05/B06 구조물 폼 — 종류 목록이 늦게 와도 고른 관 폼 다시 엶
- 까닭: 공용 폼 setTypes 가 군 · 종류를 빈칸으로 돌리고 고른 관(selectedPipeChainage)은 남김
  · B06 에서 관 고른 뒤 처음 B05 를 열면(B05 보기 첫 구조물 불러오기) 5/5 빈 폼
  · B05 에서 종류 받기 전 관을 누르면 3/5 빈 폼(카드 · 목록 고름은 남고 종류 목록 0)
- 고침: setTypes 끝에서 고른 관이 있으면 그 관 폼 다시 엶 · 임시 배치 중은 둠
  · 구조물 폼은 뒤따르는 setStructures 가 이미 다시 엶
- 시험: test_754_2_types_late_form.py
- 검증: 새 탭 54 번(B05 · B06 시작 · 이른/늦은 고름 · 예정노선 수정 켬끔 · 새로고침) 빈 폼 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:32:05 +09:00
eomsangdonandClaude Opus 5.5 a9f4b0c88d knowledge(계획): 계획 이관 736 ~ 757 — 단가산출 상세 · 서로 독립 · 불러오기 · 셈 통일 · B05 예정노선 수정
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 23:31:01 +09:00
eomsangdon 8f36cd9d55 Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_2' into main_laptop_1 2026-11-02 23:29:24 +09:00
eomsangdonandClaude Opus 5.5 9d5778bf57 feat(755-3): 예정노선 수정 켠 채 판 고침 — [저장] · [확정] 막고 알림 · 떠남 물음 · 탭 닫기 브라우저 물음
- design_save 저장 막음 자리(holdDesignSave · 화면에서 빠지면 풂) · 막음 = 저장 줄 · 가림막 앞
- ExpectedDraw = 켠 채 판 고침(사진첩 한 걸음 이상)이면 막음 글 · 공용 떠남 물음(guardProjectLeave) · 되돌려 고침 없으면 안 물음
- 시험 test_755 지킴 한 건 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:28:47 +09:00
eomsangdonandClaude Sonnet 5.5 a773a9d2cd feat(753-2): 옵션으로 빠진 줄 숨김 — 상세 · 인력재료경비 · 시험 계산 · 텍스트 수식(M01 · M02 공용) 모두 안 그림
- 753-1 서버 줄 덧칸 `옵션꺼짐` 을 화면 넷이 읽어 그 줄을 안 그림 — 공용 상세 박스(`LogicLab_Ho` boxCard · 장비 · 하위 몫은 다 빠졌을 때만) · 텍스트 수식(`LogicLab_Formula`) · 시험 계산 표(`Logic_Calc` answerView) · 공용 계산서(`calc_sheet` · M01 인력재료경비 · M02 `sheetGroups`)
- 옵션 칸 · 소계 · 금액 · 차례 · cid 그대로(옵션 칸은 빠진 줄의 쓰는 입력도 포함) — 옵션을 바꾸면 보이는 줄이 바뀜
- M02 표 줄 `CoverRow.off` · 계산서 줄 `옵션꺼짐`
- 새 지킴 test_750_2 에 753-2 대조(표 줄 off → 계산서 줄 · 소계 그대로)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 23:28:01 +09:00
eomsangdonandClaude Opus 5.5 8b3560c4ce fix(755-3): 받은 노선 원본 꼭짓점을 체인 예정노선 파일 양끝에서 자름 — 지표면 넓어진 뒤 280 m 긴 흠
- /route/plan/source = 지표면 트림 뒤 expected_route.csv 시점 · 종점 투영 자리에서 자름(파일 없음 = 그대로) · 0.1 mm 반올림 겹친 점 하나만
- 검증_sub2 source 1,377.4 → 1,097.4 m(= csv) · follow 1,356.5 → 1,080.2 m · 검증_sub1 은 전후 같음(1,097.4)
- 시험 test_635_2 양끝 자름 · 파일 없음 그대로 한 건 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:21:40 +09:00
eomsangdon 39652baf40 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-02 23:18:58 +09:00
eomsangdonandClaude Sonnet 5.5 2372181940 refactor(751-2): 불러오기 화면 직접 꼴 갈음 — CheckRows 수량 · Compare 수량 · 후보 단가 공용 꼴
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 23:18:35 +09:00
eomsangdon ad9062dec7 Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_7' into main_laptop_1 2026-11-02 23:14:46 +09:00
eomsangdonandClaude Sonnet 5.5 da45997ebe feat(752-1): 단가산출 상세 오른쪽 원문 패널 — 기초마다 탭 · 일위대가 줄 모달 안은 끔 · 좁은 폭 접힘
- 새 `Sangeun_Side` — 일위대가 상세와 같은 서랍(`createSidePanels`) + 원문 보기(`createSourceView` · M02 길 `/m02/logic-lib/source` · 원문 다운로드) · 바탕 · 둘째 기초(더한줄 로직)마다 탭 · 기초 하나 = 탭 없음 · 바탕 없음 = 「원문 연결 없음」 · 넓은 화면(1500 이상)만 처음 열림 · 열림 · 폭 기억 `m02-sangeun-side`
- Sangeun_Detail — 옵션 `source`(단가산출 화면 View 만 켬 · 줄 모달 RowSk 는 안 켬) · 「시험 계산 · 텍스트 수식 = 바탕 기초 + …」 안내 글 걷음(750-2 로 네 보기가 같은 답이라 낡음 · 키 M02_SangeunBaseViews 지움)
- 낡은 시험 고침 — test_721_find_pick(안내 걷음) · 새 지킴 test_750_2 에 752-1 대조

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 23:14:28 +09:00
eomsangdon d0e8fcb5ee Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1
# Conflicts:
#	B05_Profile/B05_Profile_UI_RouteEdit.ts
2026-11-02 23:14:27 +09:00
eomsangdonandClaude Opus 5.5 3f4fd188e8 fix(754-1): B04 등고선 · 격자 외곽 = 지금 지표면 외곽 — 어느 표현을 지어도 낡은 dtm 같이 다시 지음 · 등고선 캐시 판단에 dtm · 굳힌 격자 때
- 지표면 짓기 차례에 dtm 을 늘 맨 앞 — manifest 항목 없음 · 지문 다름이면 같이 다시 지음(화면 [서피스 생성] = 표현 하나만 짓던 길)
- 등고선 캐시 낡음 = 모델 · 같은 필터 dtm · implicit 굳힌 격자 중 하나라도 더 새것(짓기 차례 · 등고선 API 한 함수)
- 등고선 추출이 모델 · dtm 파일을 닫음 — 실패 때 열린 채 남아 Windows 에서 다시 짓기(교체)가 막히던 흠
- ptd 처럼 dtm 없던 필터도 dtm · 굳힌 격자가 생겨 ptd · implicit 확정 길 B05 · B06 열림
- 새 시험 test_754_1_contour_outline.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:13:13 +09:00
eomsangdonandClaude Opus 5.5 dfba0e7b57 feat(755-1·755-2): B05 예정노선 수정 — 켠 동안 판만 고침 · 끌 때 한 번 추적 · 재기 · 받은 노선 색 · 설명 · 위치 칸 걷음
- 755-1 켠 동안 끊김 허용(가운데 묶음 지움) · 고칠 때마다 다시 추적 걷음 · 끌 때 끊김 = 알림하고 안 끔 · 바뀜 = 확인창 → /route/follow 한 걸음 · 켠 동안 되돌리기 = 예정노선 판 사진첩(Ctrl+Z · 도구줄 단추 · bindHistoryControls local) · [잇기] 기능 표 b05 사용
- 755-2 켠 동안 재기 = D03 재기 겹(createPlanMeasure · 선 따라 · 두 점) · 받은 노선 = 분홍 점선 + 검은 테(기존 --draw-map-route-3 · theme.css 안 고침) · 켬 설명 걷음 · 위치 칸 걷음(기능 표 site b05 숨김 · 칸 자리 그리기 하나 · 찾기 이동 · toPlan 걷음)
- 낡은 시험 고침: test_626_3(되돌리기 = 판 사진첩 · join b05 사용) · test_635_3(칸 하나 · 위치 숨김 · 끌 때 한 번 · RouteEdit 줄 한도 683 → 690) · test_635_5(찾기 이동 걷음) · 새 지킴 시험 test_755_expected_flow

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:11:21 +09:00
eomsangdon d8af573f1c Merge remote-tracking branches 'origin/sub_laptop_4' and 'origin/sub_laptop_5' into main_laptop_1 2026-11-02 23:03:40 +09:00
eomsangdonandClaude Sonnet 5.5 011046c2a2 feat(757-1): 그리기 칸 긋기 단추 걷음 · 방식 넷 켬끔 = 긋기 시작 · 끝
- 분할 고르기 부품에 풂 꼴(clearable) · 부품 모음 견본에 더함
- 방식 칸(map_draw_kind)에 켬끔 꼴(onActive · setActive) — 켬 = 긋기 시작 · 고른 방식 다시 누름 = 긋기 끝 · 긋는 중 다른 방식 = 방식만 바뀜
- D03 · B05 그리기 칸 [긋기] 단추 걷음 · 긋기가 Enter · Esc · 붙어 끝나도 칸 켬 맞춤 · 노드 판은 그대로
- 시험 757_1 새로

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 23:03:15 +09:00
eomsangdonandClaude Opus 5.5 a36092fad8 feat(753-1): 옵션으로 빠진 줄 표시 — 엔진 줄 덧칸 옵션꺼짐
- 새 master_gate: 호표 줄 수량 0 이 만약 간 갈래(상수 0) 탓이면 옵션꺼짐 true
  (고르기 · 수 입력 문턱 · 중간값 거침) · 입력 0 · 표 칸 0 · 원문 0 = 칸 없음
- master_formula.run 한 곳에서 붙임 → M01 /calc · SK 셈 답 줄 저절로 실림
- master_sheet_line: /text · M02 sheet 줄에 같은 칸(KEYS)
- M01 정본 자료 안 고침 · 금액 · 차례 · cid 그대로(돈 로직 1,440 전후 같음)
- 지킴 시험 test_753_1_option_off(GC000211 폼타이 · 진짜 0 · 문턱 · 금액 · /text · SK)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:03:04 +09:00
eomsangdon 34a2b8d163 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_6 2026-11-02 23:02:41 +09:00
eomsangdonandClaude Sonnet 5.5 db590e6fa8 feat(750-2): 단가산출 보기 넷 = SK 셈 답 하나 — 시험 계산 · 텍스트 수식도 SK 답(글 · 중간) · 바탕 M01 로직 따로 셈 걷음 · 옵션 바꿈 서버 셈 한 번
- Sangeun_Detail — 텍스트 수식 = 답 `글` · 시험 계산 = 답 `줄` · `기준` · `중간`(`buildCalc` 덧칸 `answer` · `shown` · `held`) · `/logic-lib/calc` · `/text` · 미리보기 부름 · `baseCovers` · `dropText` · `applyCovers` 걷음 · 바탕 원값 셈 = `보기: false`
- 재료 고름(줄 모달 · 시험 계산 재료 칸) = SK 문서 `고름`(cid 열쇠 · 둘째 기초 세부 줄은 보기만)
- LogicLab_Ho — 읽는 식 줄을 `cid` 로 이음(`HoContext.cids` · 이름 차례 추측은 M01 만) · LogicLab_Formula — 단위 환산 `나눔` 머리 한 줄
- 낡은 시험 고침 — test_717_2b(`baseCovers` · `dropText` 단정 뺌 · 함수 지움) · test_725_1(`void recalc()` 새 꼴) · 새 지킴 test_750_2
- M01 단가산출 기초 = 덧칸 안 넘겨 옛 길 그대로

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 23:01:57 +09:00
eomsangdon 7937846caf Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into main_laptop_1 2026-11-02 23:01:25 +09:00
eomsangdonandClaude Sonnet 5.5 1288e21e55 feat(756-1): 추천 선 진하게 · 스냅 한 아이콘 · 추천노선 여럿 고르기 · 켠 동안 등고선 띠 넓힘
- 예정노선 수정 켬에도 추천 선 불투명 그대로(Render)
- 스냅 + 스냅 간격 = 고르기 메뉴 하나(측점 No. · 10 m · 끔) · 도구줄 표 snap-step 줄 걷음
- 추천노선 = 여럿 고르기(공용 부품 multi · 줄 색 점 PickMenuItem.color · 끔 줄 없음) · 선 색 --draw-map-rec-1~4 · 예정노선 가이드 선은 켬끔 한 줄
- 켠 동안 등고선 띠 = LAS 는 자르지 않음 · 도엽은 계획선 + 예정노선으로 넓힘(캔버스 · GL · 도엽 다시 받기)
- 시험 624_2 · 634_2 · 626_1 · 636_2 · 390_4 · b05_routeedit_initial 을 새 꼴로 고침 · 새 시험 756_1

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:58:28 +09:00
eomsangdonandClaude Sonnet 5.5 9e0746058e fix(751-1): 원화 꼴 흠 — M01 · M02 · 서버 글 — 공용 꼴 갈음 · 율 「4.0」 · 중간 값 · 마법사 콤마 · 서고 금액
- 파이썬 짝을 common_util/common_util_money.py 로 옮김(TS 와 같은 이름 · 서버 어디서나 불러옴)
- 시험 계산: 율 글자 「4.0 %」 → 숫자 4(서버 _pct) · 중간 값 경비 · 공구손료 등 비목 이름은 1,000 미만도 돈 꼴(870.2455 → 870)
- 기초 편집 단가 = 공용 단가 꼴 · 금액 = 소수 1 · 서고 기준 금액 · 구조물 조합 일위대가 금액 = 공용 정수 꼴(124,419.85 → 124,420)
- 마법사(확인 · 고르기 · 별칭 장부 · 엑셀 · 우리 금액): 콤마 없음 · 소수 셋 기본 · 부동소수 꼬리 걷음 · 품 비교 꼬리표 지수 꼴(1.235e+05) 걷음
- 다시 잇기 · 붙여넣기 대조 · 찾아 넣기 금액 열 끝 0 없음(22.9 원이 23 으로 숨지 않음) · 흐름 로직 텍스트 수식 비목 소계 정수
- 낡은 시험 고침: test_m02_hopyo_screen 품 비교 보통인부 0.0364 → 0.03636(수량 꼴 유효숫자 넷 규칙으로 파이썬 · TS 통일)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:54:02 +09:00
eomsangdonandClaude Opus 5.5 8a60bca9fe knowledge(요구): 분석 589-A — B05 평면 예정노선 수정 열두 가지 지금 동작 · 까닭 · 고칠 길
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 22:43:37 +09:00
eomsangdonandClaude Opus 5.5 60ff62ed3f knowledge(요구): 분석 589-B — B04 등고선이 옛 dtm 외곽 · 횡단배수 옵션 재현 안 됨(첫 열림 빈 폼 한 번)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 22:42:17 +09:00
eomsangdonandClaude Sonnet 5.5 d87c7b14fd test(740-2): M02 구조물 패널 낡은 대조 두 건 새 꼴로 — 738-1 첫 화면 갈래(단가산출 · 일위대가 · 그 밖 home) · 머리 단추 줄에 초기화 · 삭제 추가
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:35:53 +09:00
eomsangdon 5674892c8d Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-02 22:33:34 +09:00
eomsangdon 17f9585b92 Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_6' into main_laptop_1 2026-11-02 22:32:33 +09:00
eomsangdonandClaude Opus 5.5 9d2b8e4021 feat(750-1): SK 셈 답 하나에 보기 넷 — 텍스트 수식 글 · 시험 계산 중간 · 일위대가 줄 모달 줄모드
- 새 `M02_Sangeun_Views` — SK 고친 줄(`_Over`) · 같은 입력 · 같은 고름 위에서 `master_text.lines` + `annotate` = `글` · 줄마다 cid · 금액 · 소계 · 계 = SK 줄 · 기준(별도계상 뺌 · 단위 환산 ÷배 · 못 셈 까닭) · `중간` = 시험 계산 중간값
- `calc_doc` — 문서 `줄모드` true = 일위대가 그 줄 셈(`line` · 별도계상 기본) · 몸 `보기` false = 덧칸 안 지음(바탕 원값 셈)
- `master_text.lines` · `master_sheet_line.annotate` — `files` 자리에 Master 도 받음(파일 묶음 길 그대로 · M01 `/text` 답 같음)
- `lineDoc` — `줄모드: true`(일위대가 줄 모달 89,299.60 = 그 줄 금액)
- 시험 `test_717_3` — lineDoc = as_doc 대조에서 `줄모드` 칸 따로 확인(서버 as_doc 은 `line` 인자로 줄 셈 · 문서 칸 없음)
- 새 지킴 `test_750_1_sk_views` — 변형 일곱 · 참조 바탕 · 줄 모달 · 보기 false

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:32:23 +09:00
eomsangdonandClaude Sonnet 5.5 1da41d6613 test(748-2): 낡은 시험 고침 — test_717_2b 상세 부품 길 대조(cover 열기 · hoCtx)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:23:20 +09:00
eomsangdonandClaude Sonnet 5.5 27f33a8c53 fix(751-1): 원화 꼴 흠 — B09 · 일위대가표 격자 「.0」 꼬리 걷음 · 단가 소수 셋 · 환율표 · 빈 값 · 금액 정수 꼴 공용 갈음
- 격자 단가 칸 소수 셋 · 금액 칸 소수 하나 끝 0 없음(275,790.0 → 275,790)
- B09 money() 소수 자리 있으면 끝 0 없음 · 환율표 59,020.00 → 59,020 · 단가 열 소수 셋(1,846.39 · 22.9)
- 엑셀 불러오기 대조 글 빈 값 = 빈칸(0 아님) · en-US 정수 → 공용 formatMoney · 재료 창고 금액 공용 꼴

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:20:43 +09:00
eomsangdonandClaude Sonnet 5.5 b58b01a007 feat(748-3): 텍스트 수식 바닥 노무비 · 재료비 · 경비 고정 줄(계 없음) · 비목 상자 사이 간격 = 바닥 줄 위 여유
- 새 `LogicLab_Foot` — 바닥 비목 소계 줄 한 부품(단가산출 상세 4 칸 · 텍스트 수식 3 칸 계 칸 없음) · 상자 소계는 그대로
- 비목 상자 사이 간격 = 바닥 줄 위 여유(공통 값 `--m01lab-box-gap` 12px · 두 보기)
- 줄마다 붙던 옵션 값 칩 걷음은 748-2 커밋에 함께(위 옵션 칸이 대신)
- 낡은 시험 고침: test_737 바닥 줄 부품 · 칸 수 · 상자 간격 대조

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:20:09 +09:00
eomsangdon 8270441d6f Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-02 22:18:14 +09:00
eomsangdonandClaude Sonnet 5.5 b472f200b6 feat(751-2): 원화 꼴 공용 한 벌 — common_util_money.ts · master_fmt.py 짝 · 거울 시험
- formatMoney(정수) · formatLineMoney(소수 1 · 끝 0 없음) · formatPrice(단가 소수 3 · 200 미만 유효숫자 넷) · formatNumber(수량 · 율 유효숫자 넷)
- M01 Logic_Money · Logic_Edit 는 다시 내보내기만(부르는 쪽 그대로)
- 파이썬 짝 master_fmt.py(절반 올림 · 은행가 반올림 어긋남 해소) · master_text 갈아 끼움은 받기 뒤

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:18:11 +09:00
eomsangdonandClaude Sonnet 5.5 6732842a8e feat(746-2): 고르기 창 후보 블록 줄 비교 표 · 못옮김 · 안 이은 기계 줄 M01 요소 후보 단추
- 고름 대기 칸에 후보(앞 셋)마다 줄 비교 표(줄 · 엑셀 품 · 기초 품 · 꼴 배지) · 후보 고름 칸과 동기 · 칸 글에 같음 · 품 다름 수 · 판정 · 미리 고름 그대로
- 못옮김 잇기 창 · 안 이은 줄 창에 M01 요소 후보 셋 단추(누르면 이음 고름) · 안 이은 줄 창 기초 후보 칸에는 요소 후보가 안 섞이게 가름
- 새 파일 Hopyo_Compare.ts · 새 지킴 시험 test_746_2_hopyo_compare_screen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:17:48 +09:00
eomsangdon b2f2262b60 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-02 22:15:47 +09:00
eomsangdon f5a5bbd37e Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-02 22:15:45 +09:00
eomsangdonandClaude Opus 5.5 59a99c98a5 knowledge(요구): 요구 589 원문 기록 — B05 평면 예정노선 수정 · 도구줄 · 그리기 · 등고선 · B04 등고선 서피스 · 횡단배수 옵션
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 22:15:42 +09:00
eomsangdonandClaude Sonnet 5.5 0c0d0ce629 feat(748-2): 텍스트 수식 — 행 고르기 걷음 · 변수 이름 · 값 하나하나 누르면 줄 모달(748-1) · 줄마다 옵션 값 칩 걷음(748-3 한 조각)
- 텍스트 수식 줄 hover · 누름 고정(행 단위 선택) 걷음
- 변수 이름 식 = 변수 이름마다 낮은 단추 · 값 든 식 = 수 하나하나 낮은 단추 → 같은 줄의 상세 모달(적용 · 삭제 · 닫기)
- 변수가 시험 입력(옵션)이면 옵션 고르기 칩으로
- 공용 상세 `lineOpener` 새로 — M02 SK 상세 · M01 단가산출 기초 같은 길(M01 = 시험 고침 고리)
- 상세 보기 줄마다 붙던 옵션 값 칩(`쓰는입력`) 걷음 — 위 옵션 칸이 대신(748-3)
- 새 파일 `LogicLab_FormulaTokens`(조각 나누기 순수 셈) · 시험 test_748_2 · 낡은 시험 고침 test_722(inputChip 줄 걷음)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:15:33 +09:00
eomsangdon 09f241b2b4 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_3 2026-11-02 22:14:16 +09:00
eomsangdonandClaude Opus 5.5 bc34df27ec knowledge(요구): 분석 588-2 — 원화 표기 · 품셈 1-2-2 금액 단위표준 · 꼴 함수 한 벌
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 22:14:11 +09:00
eomsangdonandClaude Opus 5.5 e5fd227592 knowledge(요구): 분석 588-134 — 보기 넷 셈 길 둘 · 통일 · 원문 패널 · 옵션 0 줄 153 로직
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 22:13:15 +09:00
eomsangdonandClaude Sonnet 5.5 6af23d42e1 feat(748-1): 단가산출 상세 줄 모달 — 제목 오른쪽 연결 키 · [적용] [삭제] [닫기] 차례 · 요소 단가 정보 칸 걷음
- 제목 오른쪽 (연결 키 · 예 보통인부 (LB000002)) — 요소 · 고른 품목 · 부른 로직 키
- [적용] · [삭제](옛 빼기 이름 바꿈)를 모달 머리 [닫기] 왼쪽으로 · [기초 빼기] · 별도계상은 고침 칸 아래 그대로
- 고침 칸이 있는 모달은 요소 단가 정보 칸(이름 · 키 · 값 · 출처 · 마스터 보기) 걷음 — 자재품목 물가 넷 · 후보 목록 · 한 줄 풀이는 그대로
- M01 단가산출 기초 상세 모달도 같은 꼴([적용] [닫기])
- 낡은 시험 고침: test_722 모달 edit 인자(close, head)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 22:10:18 +09:00
eomsangdon 94e330a5f3 feat(748-0): 단가산출 둘째 기초 — 세부 품 M01 원문 · 머리 수량 칸 하나 · 되돌림 셋(기초 빼기 · 더한 항목 빼기 · 더한 항목 수량)
- 둘째 기초 세부 줄 품 칸 = 서버 `원문`(0.871 그대로) · 금액 · 비목은 상자 합 몫 그대로 · 세부 고침도 원문 몫(머리 수량 · 화면배 안 나눔)
- 더한 기초 머리 상자(새 `Sangeun_BaseHead`) — 기초마다 이름 · 수량(기준단위 몇 개) 칸 · [되돌림] · [기초 빼기]
- 새 `logic/M02_Sangeun_Undo` — 뺀더한줄 기억 · 되살림(열쇠 당김 · 밂) · 처음수량 되돌림 · 넣을 때 처음수량 적음
- 되살림 띠에 뺀 더한줄도 · 일위대가 줄 패치 뺀더한줄 이음(RowSk · LinePatch)
- 낡은 시험 고침: test_727 세부 고침 ÷수량 기대 → 원문 그대로 · test_717_2b 모델 인라인에 Undo 추가
- 새 시험 test_748_0_sk_undo(거울 helper_748_0)
2026-11-02 22:05:31 +09:00
eomsangdon 1c26a6f791 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_8' into sub_laptop_2 2026-11-02 21:49:03 +09:00
eomsangdonandClaude Opus 5.5 5cc598bdae knowledge(요구): 요구서 588 — 단가산출 상세 정리(PLAN 748 · 750~753)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 21:48:35 +09:00
eomsangdonandClaude Opus 5.5 a2da6cb069 Merge remote-tracking branch 'origin/dev' into sub_laptop_8
- 충돌 시험 셋(717-4 · 719-2 · hopyo build) = 740-1 펼침 단언 + 742-1 저장 수량 단언 합침
- 740-2 일위대가표 풍선 줄의 body(742-1 이 걷은 휘발 셈 몸) → cur

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:47:51 +09:00
eomsangdonandClaude Opus 5.5 8e54145c0e knowledge(요구): 요구 588 원문 기록 — 단가산출 상세 보기 넷 셈 통일 · 원화 표기 · 원문 보기 · 옵션 재료 · 모달 · 텍스트 수식 변수 · 간격 · 쉬러 감
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 21:47:30 +09:00
eomsangdonandClaude Opus 5.5 bf7f31fa63 fix(740-1): 747-1 겹침 맞춤 — SK 를 일위대가 줄로 펼칠 때 바탕 재료 별도계상 빈 칸 = 직접계산 적어 둠
- 까닭: 747-1 뒤 SK 셈은 바탕 재료 빈 칸 = 직접계산 · 일위대가 줄(덮개 줄 line=True)은 별도계상 기본 → 펼친 줄이 재료비를 잃음(합판거푸집 GC000211 SK 를 일위대가에 넣으면 금액 줄어듦)
- Library_Flat.sk_line: 바탕 재료 줄마다 자재표시 {별도계상: 직접계산} + 고른 두 칸 그대로
- 지킴 시험: 740-1 에 747 겹침(덮개 있음 · 없음 두 갈래 전후 금액 같음 · 고치기 전 실패 확인)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:44:49 +09:00
eomsangdon cfc070e228 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_8 2026-11-02 21:25:15 +09:00
eomsangdon 2b460854ba Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-11-02 21:18:53 +09:00
eomsangdonandClaude Opus 5.5 26b25626bd fix(747-1): M02 단가산출 바탕 재료 = M01 기초 그대로 셈 — 별도계상 빈 칸 = 직접계산
- 까닭: SK 셈이 바탕 로직 재료 줄에 일위대가 기본(별도계상 · 162-1)을 써 drop_apart 가 줄 · 재료비에서 뺌
- M02_Sangeun_Calc.resolve · run 에 line 인자 — SK = 바탕 재료 별도계상 빈 칸을 직접계산으로(고른 「별도계상」 만 밖)
- 일위대가 덮개 줄(Labor_Patch.run · Item_Sangeun._freeze_lines)은 line=True — 일위대가 기본 그대로
- 테스트1(GC000211) 재료비 1,404.81 → 36,525.06 · 계 = M01 124,419.85 · 기존 SK 17 중 GC000211 바탕 3 만 바뀜
- 새 지킴 시험 test_747_1_sk_material

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:15:03 +09:00
eomsangdonandClaude Opus 5.5 bcde009fb9 knowledge(요구): 요구 587 원문 기록 — 747 시험은 테스트1 로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 20:48:53 +09:00
eomsangdon a4b4564b9b Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-02 20:46:35 +09:00
eomsangdon 8436fc81eb Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_7 2026-11-02 20:46:23 +09:00
eomsangdon 3036ae6f90 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_7 2026-11-02 20:46:22 +09:00
eomsangdon 54551a6bf9 Merge remote-tracking branch 'origin/sub_laptop_3' into sub_laptop_7 2026-11-02 20:46:21 +09:00
eomsangdon b50ba90dba Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_7 2026-11-02 20:46:20 +09:00
eomsangdonandClaude Opus 5.5 6fe66e51e4 knowledge(요구): 요구 586 원문 기록 — M02 단가산출 재료비 수량 빔(테스트1 · 6-3-1 합판거푸집)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 20:45:56 +09:00
eomsangdonandClaude Sonnet 5.5 52c8692fdd feat(738-2 · 740-2): 단가산출 · 일위대가 상세 머리 [초기화] [삭제] · 넣은 줄 출처 꼬리표
- 머리 단추 차례 … [새로고침] [초기화] [삭제] [저장] — 새 Main_Actions(쓰기 권한 있는 층만 보임)
- [삭제] = 735 경고 창(쓰는 곳 표 · 키) → 나무 지우기 → 목록으로 · 쓰는 곳 없음 = 한 줄 물음
- [초기화] = 서버 -reset 미리 보기(걷힐 고침 수 · 뺀 줄 되살림 수) → 물음 → 그 층에 씀 → 다시 그림 ·
  저장 안 한 고침은 먼저 물음 · 옵션 · 기본정보 그대로
- 출처 꼬리표(본뜬) — 순수 logic/M02_Born_Tag · 단가산출 = 이름 옆 글 꼬리표(공용 Ho 꼬리표 칸을
  이름 줄 안으로 · 첫 줄 보임 · 둘째 줄 풍선) · 일위대가표 격자 = 비고 칸 글 + 풍선 · 줄 표 = 이름 끝
- 시험: 새 test_740_2 · 낡은 시험 4 고침(test_727 · 717_2b · 704_3 · 704_4 — 모델 · 격자 파일을
  이어 붙여 도는 시험이라 새 꼬리표 글 파일을 목록에 더함)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 20:36:15 +09:00
eomsangdonandClaude Opus 5.5 82037faeaa feat(743-1): 불러오기 엑셀 보관 안 함 — 그 불러오기 동안만 · 끝내기 · 화면 떠남 · 하루 지남 지움 · 같은 엑셀 다시 올림 · 만든 문서 엑셀 사본 걷음
- 끝냄 한 길 M02_Hopyo_Forget.finish — HP · 격자 · 맞춤셈 캐시 지움 · 만든 SC · SK 본뜬 = 출처(파일 · 때 · 누가 + 전국자재 · 복사원본)만 · SC 줄 메모 「실무 …」 걷음 · 실무수량 = 저장 수량.손 과 같을 때만 걷음(742 전 저장본은 남김 = 금액 그대로) · SK 또 = 그 HP 몫만
- 같은 엑셀 다시 올림 409 걷음(끝낸 기록 지문 안 씀 · 키 번은 그대로 이어감)
- 하루 지난 HP = 목록 숨김 · 올림 때 끝냄 · 붙여넣기 만듦 뒤 실무자료 끝냄
- 마법사 떠남(다른 화면 · 페이지 닫음) = 이 화면이 쥔 파일 지움(keepalive DELETE)
- 뒤 셈이 끝낸 HP 캐시를 다시 쓰지 않음(Fit._put) · 끝냄은 HP 먼저 지우고 캐시
- 옛 파일 정리 tidy(미리 보기 표 · --apply) — 옛 HP · 지문 기록 · HP 없는 문서 사본
- 본뜬 곳 칸 = 끝낸 실무자료는 「실무자료 파일」(originText)
- 시험: 718-1 「끝낸 같은 엑셀 409」 → 「다시 올림 새 키」 · 705-2 붙여넣기 실무자료 「남음」 → 「지움」(요구 584-2 로 뜻 바뀜) · 372-2 화면 시험 onDeleted 꼴 바뀜

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:22:42 +09:00
eomsangdonandClaude Sonnet 5.5 8596d383ec feat(745-1): 고르기 창 미리 고름 점 — 「다른 점」 1위 + 출처 같음만 · 누르기는 사용자 · 낡은 시험 하나 고침
- 서버 후보 묶음(`_grouped`)에 `미리` 칸 — 판정 「다른 점」 1위가 엑셀 인용 조항 = 기초 조항이면 · 「후보」 줄 점 없음
- 고르기 창 후보 줄 앞에 점 + 「미리 고름」 글 · 체크 · 선택은 그대로 비어 있음
- 북부청 표본(임도 · 사방) 다른 점 28 줄 전부 점 · 1위 맞음 28/28
- 시험 — 점 규칙 한 건 추가(test_m02_hopyo_match) · test_m02_precombo_scope 낡음 고침(735 뒤 삭제 확인이 askLibraryDelete 로 바뀜)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 20:18:55 +09:00
eomsangdonandClaude Opus 5.5 6893599fa9 feat(740-1): 일위대가 안 일위대가 = 사본 블록(원본 연결 끊음 · 하위 호표 끝수 그대로) · 초기화 이음 기계 다시 받음
- SC 안 SC = 안쪽 SC 몸 통째를 그 줄 참조 꼴 {층: 사본, 주인: 몸 지문, 키: 본 SC, 몸} 에 담음(Labor_Ref.copy_ref · read_ref 가 몸을 돌려줌 → 셈 · 알약 · B09 하위 대가 블록 · 지문 = 옛 참조 줄과 같은 길) · 엑셀 대조 거창 같음 14 그대로 · 같은 기초 겹침도 블록 안(400 없음)
- 저장 · 읽기 · 옮기기 · 찾아 넣기 모두 사본 블록 · 자기 자신을 담은 줄 = 안 펼침(저장 422 그대로)
- 단가산출에 SC 넣기 = 사본 블록 줄도 더한줄로 펼침(바깥 × 안 수량)
- 초기화 = 사본 몸 안까지 · 이음 기계 고정 줄(736-3) 단가 = M01 기계 한 시간 값 다시 받음
- 옮기기 = 쓰는 곳 검사 걷음(같은 자리 한 줄이라 안 줄 알약 차례 그대로) · 실 storage 보기만 = 1/3 SC-000002 사본 블록 · 전후 1,446,078 같음
- 낡은 시험 고침(까닭 = 하위 호표 참조가 사본 블록으로): m02_labor_ref(순환 대신 사본 · 복사 딸림 없음) · 733-2(딸림 재사용 = 구조물 조합 복사) · 735-1(쓰는 곳 = 구조물 조합 둘) · hopyo_build · 707-7(하위 호표 줄 = 사본 블록 · 본뜬)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:17:17 +09:00
eomsangdonandClaude Opus 5.5 6c0211af1f feat(744-1): 둘째 기초 세부 원문 품 · 되돌리기 기억(처음수량 · 뺀더한줄) — 서버
- 둘째 기초 펼침 줄 덧칸 원문 {수량, 금액, 비목} = 그 기초 1 기준단위 몫(M01 원문 품 · 머리 수량 안 곱함) · 줄 수량 · 금액 = 머리 수량 곱한 몫(상자 합) 그대로
- 더한줄 항목 처음수량 = 넣을 때 수량(없으면 저장 때 지금 수량) · SK · 일위대가 줄 패치 모두
- 뺀더한줄 [{차례, 줄}] = [기초 빼기] · 더한 항목 빼기 기억(셈 안 함 · 못 읽는 줄 버림) · 일위대가 줄 패치도
- 733 지문에서 처음수량 · 뺀더한줄 뺌 · Labor_Patch.as_doc = 셈 칸(CALC)만(화면 짝 꼴 그대로)
- 이음 = PLAN 744 아래 줄(화면 = sub2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:17:06 +09:00
eomsangdonandClaude Opus 5.5 c0319a4f58 feat(746-1): 불러오기 고름 대기 후보 블록 줄비교 · 못옮김 · 기계 줄 M01 요소 후보 · 단위 별칭 hr = 시간 · 개소 = 개
- 고름 대기 후보 앞 셋에 그 기초와 줄마다 견준 `줄비교`(같음 · 품다름 · 엑셀만 · 기초만 · 1 단위 품 · 못옮김) — 고르면 만드는 SK 와 같은 셈(`_Plan` 기록) · 판정 · 순위 · 미리 고름 그대로
- 못옮김 `후보` · 안 이은 기계 줄 `남은줄[].후보` = M01 요소 셋(새 `M02_Hopyo_Elem` — 이름 · 규격 점수 › 엑셀 단가 가까운 차례)
- 단위 모듈 짝(master_unit.py ↔ master_unit_swap.ts) 별칭 hr = 시간 · 개수 묶음에 개소 — 거울 시험 사례 셋 더함
- 실측(여덟 파일): 고름 대기 89 중 1위 줄비교 72(셋 중 하나 82) · 골든 1위 163 그대로 · 판정 수 그대로 · 개 ↔ 개소 1 블록 견줌 풀림 · 못옮김 후보 8/9 · 기계 줄 후보 32/32 · M01 로직 3,992 값 전후 같음
- 새 지킴 시험 test_746_1_hopyo_compare

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:04:48 +09:00
eomsangdonandClaude Opus 5.5 b68a246602 feat(742-1): 일위대가 안 줄 수량 = 사용자 입력 저장 · 기준단위 단가 / 수량 반영 금액 열
- 723-1 「안 줄 수량 null」 걷음 — SC 줄 · 자재 수량 {손} 저장(새 줄 1 · 빈 칸 = 「수량 입력」 · 금액 빠짐)
- 셈 차례: 층 = 알약 값 → 저장 수량 · 공사 굳힌 사본 = 굳힌 손 → 알약 → 저장(with_pills 가 {저장} 칸으로 옮김)
- 불러오기로 만든 일위대가 = 엑셀 수량을 저장 수량으로(본뜬.실무수량 같은 값 남김)
- 단가산출 안 일위대가 = 저장 수량으로 금액(수량 없음 없어짐) · B08 자재 · B09 내 자료집 저장도 같은 차례
- 일위대가표 수량 칸 = 문서에 저장(휘발 임시 칸 걷음) · 합계 열 「기준단위 단가」 · 「수량 반영 금액」
- 낡은 시험(723 · 175 · 204-2 · 702-3 · 703 · 717-4 · 719-1 · 719-2 · hopyo build · flow · labor ref)의 null 단언을
  저장 수량 단언으로 고침 — 723-1 결정이 742-1(요구 584-1)로 바뀜 · 새 지킴 시험 test_742_1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:03:48 +09:00
eomsangdon 25b3a465d7 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_2 2026-11-02 19:51:10 +09:00
eomsangdon 7a2dc068be Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-02 19:49:09 +09:00
eomsangdon 57142f4963 Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-11-02 19:48:03 +09:00
eomsangdonandClaude Opus 5.5 4c8a06559a feat(740-1): 단가산출 · 일위대가 서로 독립 — 넣기 펼침(M01 키 줄 + 덮개 사본 · 본뜬) · 초기화 길 · 옮기기 함수 · B09 덮개 줄 굳힘
- 새 M02_Library_Flat: SK 에 SK = 더한줄 로직 한 줄에 원본 몸 통째(덮개 · 뺀줄 · 고름 · 더한줄 · % 덧줄까지 같은 금액) · SC 에 SK = 로직 줄 + 패치 · SK 에 SC = 줄마다 더한줄(수량 = 저장 수량.손 › 실무수량 › 1) · 바탕 SK · 패치 SK 참조 = 덮개 겹침 · 줄 본뜬 {층, 주인, 키, 이름, 때}
- 저장 · 읽기 · 층 복사 때 서버가 펼침(못 펼친 줄 = 옛 참조 그대로) · SC 안 SC(하위 호표)는 저장 · 읽기에서 참조 그대로(엑셀 하위 호표 끝수 · B09 하위 대가) · 찾아 넣기 길만 펴 넣음
- 길: POST {kind}-flat(넣을 줄 · 못 펼침 400) · POST {kind}-reset(미리 보기 걷힐 수 · 씀)
- 초기화 = 덮개 · 뺀줄 · 더한줄 단가 걷음(안쪽 넣은 몸까지) · 항목 · 수량 · 입력값 · 고름 남김
- 옮기기 = migrate(apply) · 전후 1 단위 금액 같을 때만 씀 · 공유 storage 는 브레인
- 둘째 기초 세부 셈 = 넣은 SK 몸이면 이름 없는 SK 로(Sangeun_Calc.sub_run)
- B09: SC 사본 덮개 줄 = 굳힌 줄(굳힌 {키, 줄 …}) → 사본 셈 · 대가 블록이 덮개를 읽음(옛 산근 길은 덮개 무시 · 실측 6,451 대 M02 210,866)
- 733 지문 = 옛 참조 줄 펼친 꼴로 대조
- 낡은 시험 고침(까닭 = 단가산출이 낀 참조가 이제 펼쳐 담김): 715 store(바탕 · 사전조합 SK · 층 복사) · 717-2a 목록 바탕 · 717-4 SC 안 SK · 719-1 원본판(원본 고쳐도 그대로) · 719-2 속 일위대가(빠진 줄 없음 · 쓰지 않는 _calc_given 지움) · 725-1 SK 안 SC · 729 SK 줄 본뜬 · 733-3 쓰는 곳 = 구조물 조합(SK 안 SK 참조 없음) · 572 지문 대조 대상

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 19:47:33 +09:00
eomsangdonandClaude Opus 5.5 2fb39bd50f knowledge(요구): 분석 585 — 불러오기 항목별 맞춤 검토(지금도 줄마다 · 요소로 기초 거꾸로 찾기는 나빠짐 · 후보 블록 줄 비교 넓힘 권함)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 19:29:51 +09:00
eomsangdon 62330320c1 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-02 19:10:37 +09:00
eomsangdonandClaude Opus 5.5 005640da3d knowledge(요구): 요구 585 원문 기록 — 불러오기 맞춤 = 일위대가 항목 하나하나를 M01 요소 · 기초와 견줌 아이디어
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 19:10:33 +09:00
eomsangdon c61fe32c2c Merge remote-tracking branches 'origin/main_laptop_1', 'origin/sub_laptop_1', 'origin/sub_laptop_5' and 'origin/sub_laptop_7' into sub_laptop_8 2026-11-02 19:09:52 +09:00
eomsangdonandClaude Opus 5.5 7eb176eb00 knowledge(요구): 분석 582 둘 — 수량 · 휘발 · 비고 · 경쟁사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 19:09:05 +09:00
eomsangdonandClaude Opus 5.5 f23fab697e knowledge(요구): 요구 584 원문 기록 — 일위대가 안 줄 수량 = 사용자 입력 · 불러온 엑셀 보관 안 함 · 미리 고름 다른 점만 · 강관비계 그대로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 19:09:04 +09:00
eomsangdonandClaude Sonnet 5.5 afb9710423 feat(741-1): 불러오기 엑셀 보기 열 폭 — xlsx 파일 열 폭 · 폭 없는 열은 글 길이로 · 머리 경계 끌기 · 두 번 눌러 맞춤
- 서버: xlsx 시트 XML 열 폭 · 숨김을 보냄(엑셀 보기 격자 `열`)
- 화면: 폭 없는 열은 글 길이로 처음 폭(최소 6 · 최대 60 · 여러 열 병합 제목 칸은 뺌)
- 읽기 전용 격자도 열 폭 끌기 · 경계 두 번 누름 맞춤(보기만 · 저장 · 되돌리기 · onChange 없음)
- 열 경계 오른쪽 반에서도 경계로 읽음 · 단가 카드 미리보기는 사본으로 보임
- 시험: test_741_sheet_widths · test_stage2_autofit(esbuild 묶어 node)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 18:56:36 +09:00
eomsangdonandClaude Opus 5.5 78be3966d9 knowledge(요구): 요구 583 원문 기록 — 조사 끝나면 결정 사항 한 번에
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 18:55:16 +09:00
eomsangdonandClaude Sonnet 5.5 5e2ac2ae79 feat(736-3): 단가산출 · 일위대가 [항목 추가] 기계(M01 EQ) 고름 — 고정 줄(이음 기계 · 단가 = 한 시간 노 · 재 · 경)
- 찾아 넣기 창 기계 줄 누름 = 서버 `/logic-lib/machine-hourly` 로 한 시간 값을 받아 고정 줄 단가로
- 일위대가 = 이음 기계 고정 줄 · 단가산출 = 더한줄 고정 줄(수량 1) — 같은 금액
- 서버 `M02_Machine_Hourly`(기계경비 로직 짝 차례 첫째 · 읽기만) · 시험 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 18:44:12 +09:00
eomsangdonandClaude Opus 5.5 b1b9794946 feat(739-1): 별칭 장부 중앙 관리 — 자동 짝 = 마스터(승인) · 이 불러오기에서 이은 짝만 · 회사 · 개인 층 짝 = 내 제안
- 장부 읽는 차례: 자동 정확 열쇠(마스터 · 같은 불러오기 `불러오기` 표시) → 제안 정확 열쇠(`제안` True · 자동 아님) → 품명|단위 느슨
- 만들기 · 단가산출 만들기가 이은 짝에 불러오기 열쇠(`doc_id` 층/주인/HP 키) 남김 · 장부 화면 고침은 그 표시 그대로 둠
- 줄 맞춤: 내 제안 = 숫자 판정 그대로 + 꼬리표 「내 제안」 + `제안`(고르기 창 맨 위 · 한 번 눌러 짝 통째) · 호표 통째 제안 = 체크 안 함
- 장부 화면(회사 · 개인 층) = 「내 제안 목록」 · 승인 칸(대기 · 승인 · 반려 = M01 서명 · 반려 기억) · [삭제] → [거두기] · 마스터 층(M01) 그대로
- M01 별칭 장부 모으기 · 승인 · 반려 길 그대로 · 기존 층 장부 파일 안 지움(제안으로 읽힘)
- 낡은 시험 고침: hopyo ledger(아래 층이 이김 → 마스터만 자동 · 제안 · 승인 상태) · hopyo match(별칭 자동 규칙 = 마스터 층 장부로) · hopyo screen(머리 글 · 거두기 단추)
- 새 시험 test_739_1_alias_central(층 짝 자동 0 · 내 제안 · 이은 짝 → 제안 · M01 후보 · 승인 뒤 자동 · 반려 상태)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 18:40:45 +09:00
eomsangdon aaea39e23f Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-02 18:39:44 +09:00
eomsangdonandClaude Opus 5.5 d46fafd1ba knowledge(요구): 요구 582 원문 기록 — 단가산출 기초 수량 원복 · 불러오기 수량 뜻 다시 · 비고 열 · 불러온 자료 휘발 · 경쟁사 REA 분석 · 엑셀 열 폭
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 18:39:41 +09:00
eomsangdon aa26860358 Merge remote-tracking branches 'origin/main_laptop_1', 'origin/sub_laptop_2' and 'origin/sub_laptop_6' into sub_laptop_3 2026-11-02 18:32:24 +09:00
eomsangdonandClaude Opus 5.5 14b0b4ae7d knowledge(요구): 요구 581 원문 기록 · 분석 579-2 — 단가산출 · 일위대가 서로 독립 · 연결은 M01 키만 · 초기화 = M01 키 값으로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 18:31:35 +09:00
eomsangdonandClaude Sonnet 5.5 10984109b4 feat(737): 단가산출 상세 옵션 6 열 격자 · 합계 한 줄 바닥 고정 · 텍스트 수식 비목 상자 · 두 보기 같은 제목
- 옵션 칸 = 상자 폭 기준 1 → 2 → 3 → 4 → 6 열(calc_sheet optionGrid · container query) · 공통 옵션 따로 상자 걷고 모든 상자 맨 위 옵션 칸에 같이
- 맨 아래 노무비 · 재료비 · 경비 · 계 = 한 줄 4 칸 · 화면(모달) 바닥 sticky · 메인 칸 overflow clip(굴림 칸이면 안 붙음)
- 텍스트 수식 = 비목 구분 상자 · 상자 위 옵션 칸 · 줄마다 붙던 옵션 꼬리표 걷음
- 두 보기 상자 제목 「단가산출 상세」 · 「지금 입력」 꼬리표 걷음
- 신규 지킴 시험 test_737_option_grid_foot

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 18:25:09 +09:00
eomsangdonandClaude Sonnet 5.5 baeed9d18e feat(738-1): M02 들어올 때 단가산출 · 일위대가 = 그 컨테이너 기본 층(개인) 목록표 · 구조물 조합 길 그대로
시험 test_738_m02_entry_personal 추가 · test_m02_precombo_scope 한 건(삭제 확인 글 찾기)은 이 일과 무관한 기존 실패

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 18:13:23 +09:00
eomsangdonandClaude Opus 5.5 36f24abf8a knowledge(요구): 요구 580 원문 기록 — 별칭 장부 중앙 관리 진행 · 앞으로 조사 뒤 바로 작업 · 고를 것만 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 18:08:47 +09:00
eomsangdonandClaude Opus 5.5 88815881da knowledge(요구): 요구 579 원문 기록 — M02 열기 기본 개인 층 · 검색 추가 = 출처 기억 + 수정 가능 사본 · 단가산출 상세 삭제 단추
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 18:06:03 +09:00
eomsangdonandClaude Opus 5.5 7f0472e964 knowledge(요구): 요구 578 원문 기록 — 상자 제목 단가산출 상세 · 입력 꼬리표 걷음 · 공통 옵션 구분 상자에 같이
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 17:56:29 +09:00
eomsangdon 0ae48d0a89 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-02 17:53:39 +09:00
eomsangdonandClaude Opus 5.5 3830afc7be knowledge(요구): 요구 577 원문 기록 — 텍스트 수식 보기 구분 상자 · 상단 옵션 6 열 · 항목별 옵션 꼬리표 걷음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 17:53:32 +09:00
eomsangdonandClaude Opus 5.5 56204ae91a knowledge(요구): 요구 576 원문 기록 — 단가산출 상세 옵션 6 열 · 합계 한 줄 바닥 고정
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 17:50:37 +09:00
eomsangdonandClaude Sonnet 5.5 bc479bb3d0 feat(736-2): 빠진 길 둘 — 찾아 넣기 창 [신규 자재](개인 층) · 기계 탭 · 엑셀 연결 줄 갈래대로 탭
- 개인 층 재료 고름 = 창 머리 [신규 자재] — 만들어 바로 자재 목록에 넣음(`openNewMaterial`)
- 찾아 넣기 창 기계(M01 EQ) 탭 — 「M01 전체」 묶음 안 · 부르는 쪽이 `machine` 을 줄 때만 보임
- 엑셀 연결 = 줄 갈래대로 탭(노임 = 인력 · 기계 = 기계 · 자재 = 재료 · 그 밖 = 기초) — 불러오기 고르기 · 남은 줄 · 못옮김 · 안 이은 줄 연결 모두 같은 창 · 따로 [기계] 단추 걷음
- 낡은 시험 고침(721 · 729-3: 기계 고름 길이 찾아 넣기 창으로 옮김) + 736-2 글 시험 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 17:42:05 +09:00
eomsangdonandClaude Sonnet 5.5 f97ea96c08 feat(736-1): 일위대가 [항목 추가] 하나 · 찾아 넣기 창 하나로 — 자재 추가 단추 걷음 · 엑셀 연결 · 별칭 장부 · 견본
- 일위대가표 · 구조물 조합 상세 [로직 추가] → [항목 추가] · [자재 추가] 걷음 · 재료 고름 = 자재 목록(로직 안 자재 · 이미 있는 자재 막힘 그대로)
- 찾아 넣기 창 탭 막기(kinds) · 찾기 칸 미리 넣기(query) · 재료 길(material)
- 불러오기 엑셀 연결 = 「다른 항목 검색…」 한 창(기초 · 재료) · 별칭 장부 = 기초 · 재료 둘
- 부품 모음 단추 견본에 갈래 색(옵션값 · 요소 연결) 칸
- 낡은 시험 고침: 175(머리 단추) · 721(부르는 곳) · 자재 개인(상세 단추) · 호표 화면(장부 단추 글) · 헬퍼 넷(Find_Pick 대역)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 16:50:34 +09:00
eomsangdonandClaude Opus 5.5 f82e64ce29 knowledge(요구): 요구 575 원문 기록 — 답 다섯(찾기 창 하나로 동의 · 랩탑메인 그대로 · 보기 넷 유지 · lidar_lab 지움 · 견본 물음 모름)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHFZkNcUa1SR4cNcENM
2026-11-02 16:14:27 +09:00
eomsangdonandClaude Opus 5.5 5b9a1e8720 knowledge(계획): PLAN 734 · 735 이관 — 계산식 열 단추 · 일위대가 삭제 경고 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:53:51 +09:00
eomsangdonandClaude Sonnet 5.5 47af68b6c4 fix(ui-kit): 지도 도구줄 · 범위 바 부품에 숨김 짝 규칙 더함
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 15:53:13 +09:00
eomsangdon 1bdd3ce99a Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-02 15:51:39 +09:00
eomsangdonandClaude Opus 5.5 1d8642fd76 feat(735-1): 서고 나무 지우기 — 쓰는 곳 경고 표 뒤 그 줄째 지움 · 되돌림
- 새 M02_Library_Delete: 쓰는 줄 미리 보기(종류 · 이름 · 키 · 줄 · 층) · 권한 밖 409 막힘 목록 ·
  확인 몸이면 쓰는 문서의 그 줄 지움(store.write 정본 다시 셈) 뒤 문서 지움 · _합친것/ 기록
  (지운 문서 · 자리 · 고친 문서 앞판) · 되돌림(그새 고친 문서는 건너뜀) · 단가산출 바탕 · 더한줄 열쇠 당김
- 미리 보기 길 POST {kind}-tree/delete-check · undo 길이 지움 기록도 되돌림 · 합친 것 목록에 「지움」
- 나무 고침 몸 확인 칸 · 세션을 나무 고침에 넘김 · 옛 _refuse_used 걷음
- 화면: 지우기 앞 경고 창(쓰는 곳 N · 표 · 프로젝트 사본 영향 없음 · 되돌림 자리) · 부품 상자
  확인창에 detail 칸(넓은 판 · 안 스크롤)
- 시험: 새 test_735_1 · test_716_4 의 쓰는 곳 409 시험을 확인 없음 409 + 목록 꼴로 고침
  (지움이 막힘에서 경고 뒤 지움으로 바뀜)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:50:56 +09:00
eomsangdon e4f4dc2b33 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-02 15:40:35 +09:00
eomsangdonandClaude Sonnet 5.5 8cd30cc64d feat(734-1): 계산식 열 단추 낮은 단(20) · 옵션값 / 요소 연결 갈래 색 · 풍선 글
- 부품 상자 단추에 갈래 색(tone: option · link) 더함 — 공통 값(--color-warning · --color-info) 혼합 · 밝음 4.5 이상 · 어두움 6 이상
- 알약(요소 연결) · 입력 칩(옵션값) 24 → 20 · 마우스 풍선 「요소 연결」 · 「옵션」
- 지킴 시험 test_734_calc_buttons 새로

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 15:40:02 +09:00
eomsangdonandClaude Opus 5.5 bc9dcb865a knowledge(요구): 요구 574 원문 기록 — 일위대가 나무 삭제 경고 뒤 삭제 · 참조 줄 지움 · 4173 은 사용자 요청 때만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:32:15 +09:00
eomsangdonandClaude Opus 5.5 1e8d05cea1 knowledge(요구): 요구 573 원문 기록 — 단가산출 상세 계산식 열 단추 낮게 · 색 구분
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:27:38 +09:00
eomsangdonandClaude Opus 5.5 a72673cd1c knowledge(계획): PLAN 730~733 이관 — 엑셀 보기 테두리 · 모바일 · 일위대가 줄 삭제 · 실무문서 칸 걷음 · 중복 확인 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:18:30 +09:00
eomsangdon 91137a6196 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-02 14:15:24 +09:00
eomsangdonandClaude Opus 5.5 42de907aa2 feat(733-1b): 불러오기 단가산출 같은 내용 SK 씀 · 위 층 같은 것 알림 · 결과 화면 꼬리표
- M02_Hopyo_Sangeun — Maker.write 가 이 층 같은 내용 SK(M02_Library_Same 지문)가 있으면 새로 안 만들고 그것을 씀 · 이름 = 먼저 만든 것 · 뒤 출처 = 본뜬.또[그 블록 본뜬 + 이름] · made 가 또 읽음 · origin_of = 출처마다 갈래 · 못옮김 · 놓음
- 다른 출처가 쓰는 SK 내용은 안 바꿈 — 고른 기초 · 잇기로 바뀌면 떼어 냄(앞 출처면 뒤 출처에게 넘겨줌) · 혼자 쓰던 SK 만 제자리 덮음
- 응답 같은것씀 [{번, 키}] · 위층같음 [{번, 층, 주인, 키}](회사 · 마스터 · 알림만)
- M02_Hopyo_SkLink — 못옮김 · 갈래를 그 블록 출처에서 읽음(같은 내용이라도 엑셀마다 못옮김이 다름 · 금액 지킴)
- 불러오기 ② 단가산출 표 갈래 칸 꼬리표 「같은 것 씀」 · 「위 층에 같은 것 — 연결 권함」 · 요약 글에 수
- 새 시험 test_572_hopyo_same.py — 바이트만 다른 사본 새 SK 0 · 위 층 알림 · 출처마다 못옮김 · 떼어 냄 · 넘겨줌
- 낡은 시험 고침: test_718_1(만든 SK 수 = 만듦 수 가정 → 같은 내용 SK 한 벌이라 문서 수 + 같은것씀 = 만듦) · test_729(_sk_docs 가 본뜬 키만 봄 → 또 출처도 블록에 매김)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:14:42 +09:00
eomsangdonandClaude Opus 5.5 4de5a3416b feat(733-3): 같은 내용 물음 — 호표 [구조물 조합으로]
- buildHopyoItem 에 다시 보냄 덧칸(씀 · 새로) · 물을 때는 가림막 걷음 · 그것을 씀 = 「새로 안 만듦」 알림

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:58:31 +09:00
eomsangdon a42f44ec26 Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-11-02 13:57:14 +09:00
eomsangdonandClaude Opus 5.5 2604a5073a feat(733-3): 같은 내용 물음 화면 — 단가산출로 저장 · 공유 승인 · B09 내 자료집
- 부품 모달에 갈래 묻기 askChoice(취소 + 단추 여럿 · 고른 값 · 취소 = null) · 부품 모음 견본 한 칸
- 새 M02_MasterTemplete_Library_SameAsk.ts — 409 같은것 → 「그것을 씀 / 그래도 새로」 → 몸 씀 · 새로 로 다시 부름
- from-line(단가산출로도 저장 · 단가산출로 저장) · 공유 승인 · B09 내 자료집에 물음 · 취소 = 저장 안 함
- 새로 만들기(나무 · 목록 [생성])는 빈 문서라 서버가 묻지 않음 — 화면 손대지 않음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:57:07 +09:00
eomsangdon 746a1f6050 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-02 13:55:08 +09:00
eomsangdonandClaude Opus 5.5 40980910fa feat(733-2): 사람 길 만들 때 같은 내용 409 물음 · 복사 딸림 재사용
- 새 M02_Library_Ask — 같은 층 같은 내용(733-1 지문) = 409 {같은것: [{키, 이름, 층, 주인}], 까닭} · 몸 새로 = 그래도 새로 · 씀 = 그 키(새 문서 0 · 같은 내용 아닌 키 = 다시 409)
- 길 = 새로 만들기(빈 문서 · 같은 층 복사 = 안 물음) · [단가산출로 저장] from-line · B09 내 자료집(같은이름 409 와 한 몸 · 하위 호표 지문 = 굳힌 문서) · 공유 요청 승인(일위대가 · 씀 = 사본 키) · 실무 호표 [구조물 조합으로](구조물 조합 지문 = 줄 수량 넣음)
- 복사 딸림 carry — 받는 층에 같은 원본 + 같은 내용 사본이 있으면 그 키 재사용(refs.copied_from) · B09 하위 호표 재사용 목록 = 실무자료 한정 → 모든 사본 원본
- 낡은 시험 고침: test_703 자료집 같은 이름 물음 — 새 이름이라도 같은 내용이면 409(정함 3장 3) → new=True 로 새로 · 씀 확인 더함
- 새 시험 test_733_2_library_ask(새로 · from-line · 승인 · carry) · test_705_2 구조물 조합으로 409

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:54:28 +09:00
eomsangdonandClaude Opus 5.5 320db86802 fix(733-3): 중복 찾기 창 — 꼬리표 배지 늘어남 · 합치기 단추 크기 · 그새 바뀜 토스트 글
- 묶음 머리 이름 칸 CSS 가 배지(span)까지 늘리던 것 — 이름 칸 class 로 좁힘
- [합치기] = 기본 단추 크기 · 판 다름 409 = 「그새 바뀜 — 다시 찾음」만(HTTP 409 꼬리 뺌) 뒤 다시 찾음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:53:24 +09:00
eomsangdonandClaude Opus 5.5 b9e6f77dab feat(733-3): 서고 중복 찾기 · 합치기 서버 등록 · 지문 모듈 연결 · 시험
- M02_Library_Same_Router — 733-1a 지문(Same 한 벌 · above) 으로 묶음 · 합칠 때 지문 다시 셈(다르면 409)
- main.py 에 라우터 등록(로그인만 · 층 쓰기 권한은 길 안에서)
- 새 시험 test_733_3_library_same_merge(합치기 참조 옮김 · 금액 같음 · 되돌림 · 지문 · 판 409 · 권한 밖 남김 · 위 층 키 · 일반 사용자 회사 층 403 · SC 수량만 다름)
- 새 시험 test_733_3_library_same_screen(부품만 · 날 색 없음 · 나무 메뉴 · 서버 길 짝)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:44:00 +09:00
eomsangdon 16c7b2c782 Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-11-02 13:40:03 +09:00
eomsangdonandClaude Opus 5.5 6cf57dc289 feat(733-3): 서고 중복 찾기 틀 — 서버 길(목록 · 합치기 · 되돌림) · 화면 창 · 나무 메뉴 (등록 전)
- 새 M02_Library_Same_Router.py — GET {kind}-same · POST merge · POST undo (main.py 등록 · 시험은 다음 걸음)
- 새 M02_MasterTemplete_Library_Same.ts · .css — 묶음 표 · 남김 고르기 · 합치기 물음 · 합친 것 되돌림
- 서고 나무 뿌리 메뉴 [중복 찾기]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:39:47 +09:00
eomsangdonandClaude Opus 5.5 8588743906 feat(733-1a): 서고 문서 내용 지문 · 같은 층 · 위 층 같은 것 찾기
- 새 M02_Library_Same.py — 단가산출(SK) · 일위대가(SC) 내용 지문(derived_key) · 이름 · 본뜬 · 메모 · 바탕판 · 원값 · 고정 출처 뺌 · 수 · 단위 정규화 · 줄 차례 무시 · SC 수량 null(비율 줄 율만) · 빈 문서 None · 참조 = 가리킨 문서 지문
- Same 한 벌 = 한 요청 안 메모 · find_same(folder, kind, doc, skip) = 같은 층 키 · find_above = 위 층 참조
- 새 시험 test_572_library_same.py — 지문 규칙 · 같은 층 · 위 층 · 참조 지문

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:38:07 +09:00
eomsangdonandClaude Opus 5.5 8c5ef34365 knowledge(요구): 요구서 572 · 분석 — M02 단가산출 · 일위대가 중복 확인(PLAN 733)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:29:33 +09:00
eomsangdon 3449a434da Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_5' into main_laptop_1 2026-11-02 13:24:26 +09:00
eomsangdonandClaude Sonnet 5.5 d386198f7f fix(730-2): 엑셀 보기 좁은 폭(900 px 이하)에서 시트 위 · 목록 아래로 쌓음
- 390 px 에서 오른쪽 목록 22rem 이 시트 칸을 폭 0 으로 짓누름 → 좁은 폭에서 한 열 · 두 줄(시트 3 : 목록 2)
- 넓은 폭(1366 · 1920) 생김 그대로
- 지킴 시험 test_730_2_hopyo_sheet_narrow.py 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 13:22:59 +09:00
eomsangdonandClaude Sonnet 5.5 5f8f909b65 fix(731-1): 일위대가표 줄 끝 ⋯ 단추 — 우클릭 · 길게 누름 없이도 위로 · 아래로 · 별칭 · 삭제
- 마우스 우클릭 · 길게 누름 삭제는 개인 · 마스터 층 모두 이미 되었음(재현 0) — 삭제 길이 눈에 안 보이는 것이 까닭
- 공용 표(ui_template_data_table)에 rowMenu 옵션 추가(첫 칸 오른쪽 ⋯ · 누르면 줄 누름 없이 메뉴만) · 일위대가표가 같은 메뉴를 연결
- 지킴 시험 test_ui_data_table 에 rowMenu 한 건

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 13:19:34 +09:00
eomsangdon 5b3ffe5089 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-02 13:17:54 +09:00
eomsangdonandClaude Sonnet 5.5 e1b17a523c feat(732-1): M02 실무문서 칸 · 복사 탭 · 서버 길 걷음
- 왼쪽 「실무문서」 칸(Practice.ts) · 일위대가 가져오기 「실무문서」 탭 · 복사 원본 고르기 · 낱 CSS · locale 열쇠 걷음
- 서버 `from_layer=practice`(_copy_practice) · `DELETE /api/m02/practice`(drop_practice) · 조합 목록 · 키 하나 읽기 길 걷음 · 조합 길 시스템 관리자 문 걷음(담을 로직 읽기는 그대로)
- 옛 마스터 조합 파일 `일위대가조합.json`(UA 37) = 쓰는 곳 있음(구조물집계표 일위대가 칸 · labor 본뜬키 · 프로젝트 초기값 사본 · 불러오기 맞춤 씨앗) → 파일 · 읽기 길 유지 · 지우지 않음
- 낡은 시험: test_m02_practice 지움(화면 · 길 없음) · 조합 길 읽던 시험(combo · combo_inputs · combo_layers)은 저장소 함수로 · 실무문서 복사 시험(labor_copy · pair_copy) 줄 지움 · 탭 · 차례 단정(layer_screen · structure_options · structure_panel · 720 · precombo_scope · ui_loading) 고침

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 13:17:24 +09:00
eomsangdon 934c5c730d Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-02 13:08:10 +09:00
eomsangdonandClaude Sonnet 5.5 fe53e0c71d fix(730-1): 엑셀 격자 고른 범위 · 참조 테두리 · 채우기 손잡이를 굴리는 영역 밖에서 자름
- 굴림 때 테두리가 머리 칸 · 모서리 · 엑셀 화면 밖으로 벗어나던 흠(요구 566)
- 테두리 층에 clip-path(고정 칸 · 굴리는 영역 크기 기준) · 손잡이는 밖이면 감춤
- 지킴 시험 test_730_grid_sel_clip 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 13:07:33 +09:00
eomsangdonandClaude Opus 5.5 e1ce919daa knowledge(요구): 요구 572 원문 기록 — 중복 확인 기능 조사 뒤 수정
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:06:58 +09:00
eomsangdonandClaude Opus 5.5 4222481cb3 knowledge(요구): 요구 571 원문 기록 — 실무문서 복사 · 생성 중복 거름 의견
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:01:28 +09:00
eomsangdonandClaude Opus 5.5 c7d82a6519 knowledge(요구): 요구 570 원문 기록 — 불러오기 엑셀 연결 찾기 = 전체 찾기 일원화 의견
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:59:57 +09:00
eomsangdonandClaude Opus 5.5 788b0c36f1 knowledge(요구): 요구 569 원문 기록 — M02 실무문서 컨테이너 걷음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:58:05 +09:00
eomsangdonandClaude Opus 5.5 23b9757cea knowledge(요구): 요구 568 원문 기록 — 일위대가표 로직 추가 → 항목 추가 · 자재 추가 걷음 의견
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:55:20 +09:00
eomsangdonandClaude Opus 5.5 a4b636528e knowledge(요구): 요구 567 원문 기록 — M02 일위대가 항목 삭제 · 실무문서 컨테이너 역할
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:53:53 +09:00
eomsangdonandClaude Opus 5.5 5afe82d430 knowledge(요구): 요구 566 원문 기록 — 일위대가 불러오기 엑셀 보기 고른 칸 테두리 벗어남
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:50:02 +09:00
eomsangdonandClaude Opus 5.5 7c70559374 knowledge(계획): PLAN 726~729 이관 — 단가산출 기초 이름 · 기초 넣기 · 단위 환산 · 실무문서 연결 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:38:31 +09:00
eomsangdonandClaude Opus 5.5 8aba750fec knowledge(요구): 요구 565 원문 기록 — 개발 반영 뒤 랩탑 4173 다시 켬
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:36:19 +09:00
eomsangdonandClaude Opus 5.5 33b7b93537 fix(729-3): 기계 찾기 일반 사용자 403 · 일위대가표(Labor_Table) 안 이은 줄 띠 · 띠 글
- 남은 줄 · 못옮김 · [연결] 기계 찾기 = M02 로그인 길 /api/m02/logic-lib/elements(옛 M01 길은 시스템 관리자만 = 일반 사용자 403 · 0 건)
- 서고 일위대가 상세 표(Labor_Table)에도 「M01 연결 없음 n 줄」 띠 · [연결](Labor_Grid 는 구조물 조합 상세만)
- 띠 글에서 「옛 자료」 뺌(우리에 없음 고른 고정 줄도 셈)
- 시험 test_729_3 소스 확인 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:33:02 +09:00
eomsangdon 56b3247435 Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_5' into main_laptop_1 2026-11-02 12:14:22 +09:00
eomsangdonandClaude Opus 5.5 0c8c31cadd fix(729-5): 별칭 체크 줄이 못옮김 남은 차이 SK 를 가리키면 남은 줄 · 만들기 응답 남은못옮김
- M02_Hopyo_SkLink.kept: 확인한 로직 줄이 차이 SK 바탕일 때 그 SK 에 못옮김이 남으면 남은 줄 {까닭, 단산, SK, 못옮김}(sk_line 과 같은 규칙) — hopyo-unlinked 에 나오고 hopyo-build 409
- M02_Hopyo_Sangeun.build: 응답에 남은못옮김 [{번, 이름, 키, 못옮김}](그대로 SK 중 못옮김 남은 것) · 잇기만 보냈는데 1위 기초가 고름 대기면 그 SK 바탕으로 다시 셈(고른 기초 SK 잇기)
- 시험: test_729 에 별칭 체크 줄 · 그대로 SK 남은못옮김 · 잇기 뒤 사라짐 · 차이 SK 참조

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:13:54 +09:00
eomsangdonandClaude Opus 5.5 fe78849463 feat(729-3): 단가산출 못옮김 잇기 창 · 단위 성격 다른 후보 계수 칸 · 남은 줄 표 못옮김 단추(729-4 이음)
- 새 Hopyo_Lost: 못옮김 줄마다 찾기(노임 LB · 자재 MT) · 기계(EQ) · 우리에 없음(뺌) → 캐시 m02-hopyo-lost 모두를 [잇기 만들기] 한 번에(서버가 부를 때마다 그 블록 잇기 전부로 다시 셈) · 바탕 = 그 SK 문서 바탕(로직 · 입력값 · 환산)
- 단가산출 칸: 만든 줄 표 못옮김 열 · [잇기 (n)] · 만든 줄 모아 둠 · 고름 대기 후보 계수(성격 다른 단위) = 계수 칸 → 환산 {계수} · 729-5 남은못옮김 칸 미리 받음
- 안 이은 줄 표: 못옮김 남은 SK 를 쓰는 줄(단산 · SK · 못옮김) = [못옮김 잇기 (n)]
- 시험 test_729_3_hopyo_left_screen 소스 확인 더함(계수 · 잇기 모두 · 바탕)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:57:47 +09:00
eomsangdon ee2c5b1de3 Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_5' into main_laptop_1 2026-11-02 11:34:04 +09:00
eomsangdonandClaude Opus 5.5 b63d000822 fix(729-4): 단가산출 만들기 단위 성격 다름 500 걷음 · 못옮김 잇기 서버 길
- 728-2 단위 환산 뒤 [단가산출 만들기] 500 · 400(100개 ↔ ㎡ · ㎥ ↔ m2 · 본 ↔ M · 개소 ↔ m) 걷음 — 견줌 SK 배 = ratio_of(728 규칙) · 성격 다름 = 환산 계수 = 우리 품 ÷ 엑셀 품(인력 · 장비 둘 이상 같은 비일 때만) · 못 구함 = 고름 대기(까닭 「단위 성격 다름 · 계수 필요」 · 후보[].계수 칸)
- 맞음 블록도 단위 견줌(전엔 셈 없이 기초로 써서 뒤 셈에서 막힘)
- 못옮김 = [{id, 갈래 노임|기계|자재, 이름, 규격, 단위, 수량, 글, 후보}] · 응답 만듦[].못옮김 · 환산
- 몸 바탕[번] = {로직?, 입력값?, 환산?, 잇기?} · 잇기 [{id, 노임|기계|자재: 키} | {id, 뺌}] → SK 더한줄 인력 · 기계 · 재료(엑셀 수량 × 배) · 뺌 = 본뜬 놓음 · 틀린 id · 키 400
- 못옮김 남은 SK 를 쓰는 일위대가 = 남은줄(단산 · SK · 못옮김) · hopyo-build · build-all 409
- 못옮김 남은 블록 = SK(같지 않음)
- 시험 test_729 에 단위 · 잇기 · 409 시험 하나 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:33:31 +09:00
eomsangdonandClaude Opus 5.5 1e6ec26dd0 feat(729-3): 불러오기 마법사 안 이은 줄 고르기 · [끝내기] 막음 · 단가산출 단계 · 일위대가표 띠 · 목록 열
- 새 Hopyo_Left: 안 만든 호표마다 /hopyo-unlinked 남은 줄 표 · 줄마다 고름(고르기 창 · 찾기 = 기초 · 노임 · 자재 · 기계 EQ · % 밑수 · 우리에 없음 · 고름 대기 후보 → SK 만들기) = 확인 창 캐시 → [저장] · 생성 몸 그대로
- 마법사: 단가산출 칸(새 Hopyo_WizardSk — 기초 · SK 차이 있음 갈래 표 · 고름 대기 후보 고름 → 고른 기초로 만들기 · 별칭 제안 + M01 별칭 장부 링크) · 안 이은 줄 띠 · [끝내기] = 다시 셈 → 남으면 막음 + 표
- 확인 창 · 엑셀 보기 창: 만들기 409 {까닭, 남은줄} → 고름 표 · [고르기 (n)] · 대상 꼴 노임 · 기계 · 비율 · % 줄 밑수 고른 것만 체크
- Api_Fetch StaleError.detail(409 몸) · 까닭 글
- 새 Labor_Unlinked: 일위대가표 「M01 연결 없음 n 줄(옛 자료)」 띠 · [연결] = 고정 줄 이음(노임 · 자재 · 기계)만 · 목록 열 「안 이은 줄」
- 시험: 새 test_729_3_hopyo_left_screen(409 → 남은 줄 · 만든 호표 안 물음 · 고름 → 몸 · 0 · 띠 줄 · 소스) · 낡은 시험 고침 — test_m02_hopyo_screen(마법사 /sangeun 부름이 단가산출 칸으로 옮김) · 헬퍼 셋(새 Hopyo_Left 를 대역 — DOM · 찾기 창)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:25:45 +09:00
eomsangdon a70539c161 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_3' into sub_laptop_5 2026-11-02 10:58:09 +09:00
eomsangdonandClaude Opus 5.5 08ad680188 feat(728-2): SK 단위 환산 — 기초 기준단위 제안 · 바꾸면 배 · 계수 칸 · 둘째 기초 수량 처음값 · 돈 아닌 기초 뺌
- 서버 Sangeun_Calc: 배 = 바탕 기준단위 → SK 단위(master_unit.swap) · 성격 다름 = 문서 환산 {계수 | 단위중량} · 없으면 셈 · 저장 막음(까닭 글) · 「식」 · 빈 단위 = 옛 셈 그대로(일위대가 줄 모드)
- 화면 셈 답 줄 · 계 = SK 1 단위 몫(per_unit · 화면배) · 머리 띠 「100본 당」 · 「1 본 당」
- 서버 Sangeun_Doc: 환산 칸 검사(계수 양수 · 단위중량 종별 · 형상)
- B09 Labor_Source: SK 참조 배 = 같은 ratio_of(옛 바탕 앞 수 대신 · 기존 문서 같은 값)
- 화면 Sangeun_Detail: SK 단위 처음 값 = 기초 기준단위(「100본」) · 둘째 기초 수량 처음값 = 바탕 기준단위 ÷ 그 기초 기준단위(swapUnit) · 계 칸 원/SK 단위
- 새 Sangeun_Unit: 근거 글 · 환산 계수 칸 · 단위중량 표(CC000337) 고르기
- Cover_Model: 화면 품 × 화면배 = 문서 품(덮개 · 더한줄 · 세부) · 율 그대로
- Find_Pick: 돈 아닌 기초(결과단위 「원」 아님 21 개) 목록에서 뺌
- 낡은 시험 test_715_sangeun_engine(바탕 원/주 + SK ㎡) · test_727(바탕 원/㎡ + SK ㎥) = 성격 다른 단위가 이제 계수 없으면 막혀 SK 단위를 바탕 단위로 고침(금액 같음)
- 새 시험 test_728_2_sk_unit(100본 → 본 1,538.89 · ㎡ 44,791.32 · 계수 · 단위중량 · 식 · 둘째 기초 · 돈 아닌 기초 · 고침 꼴)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:52:05 +09:00
eomsangdon 5843c0343d Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-02 10:51:53 +09:00
eomsangdonandClaude Opus 5.5 68820e80f3 feat(729-2): 일위대가 불러오기 이음 — 단가산출 줄 = SK 참조 · 노임 이름 · 기계 EQ · 사용자 이음 · 남은 줄 있으면 만들기 막음
- M02_Hopyo_Build · 새 M02_Hopyo_SkLink: 확인 안 한 단가산출 줄 = 그 블록 SK 참조(없으면 그 블록만 만듦) · 확인한 로직이 차이 SK 바탕이면 SK 참조 · 남은 줄 있으면 409 {까닭, 남은줄} · 미리 보기 unlinked(쓰기 없음) · build_all strict = 남은 줄 호표 실패(붙여넣기 길은 막지 않음)
- M02_Hopyo_Fixed: Linker by_name(불러오기 길만) = 노임 이름 같은 LB · 기계 이름 · 규격 같은 EQ · 사용자 이음 force {노임|기계} · {비율} · linked()
- M02_Labor_Router: hopyo-unlinked 길 · 대상 칸 노임 · 기계 · 비율 · 단가산출 몸 바탕 · hopyo-build-all strict
- M02_Labor_Ref: 일위대가 목록 summary 안이은줄
- 시험: 새 test_729_hopyo_sk_link(북부청 기초잡석 · 철근 Type-Ⅰ · Ⅱ · 되메우기 SK 품 = 엑셀 품 · 기초 + 별칭 · 고름 대기 · 막힘 · 노임 · 기계 이음) · 낡은 시험 고침 — 718-1(모두 들여오기 = 남은 줄 호표 실패 · 단가산출 새 꼴) · hopyo_build · flow(남은 줄 「우리에 없음」 고른 뒤 만듦 · 빈 몸 = 409) · 705-3 · 705-2(모두 고정 = 막지 않는 build_all 직접)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:51:18 +09:00
eomsangdonandClaude Opus 5.5 8a0fd4d03d feat(729-1): 실무 단가산출 → 기초 · SK · 고름 대기 — 블록 맞춤 판정 → 엑셀 품 그대로 덮개 · 더한줄 · 뺀줄 · 별칭 제안
- 새 M02_Hopyo_SkPatch: 판정 맞음 = 기초(바탕 = 1위 기초 · 덮개 없음) · 다른 점 = 바탕 기초 + 인력 덮개(qty · 배율) · 엑셀만 직종 · 장비(GC000267 · GC000268 기계 옵션 찾기) 더한줄 · 우리만 줄 뺀줄 · 블록 안 자재 덮개 · 옮기지 못한 차이 = 본뜬.못옮김 · 후보 = 고름 대기
- M02_Hopyo_Sangeun: 바탕 없는 SK + 고정 한 줄 걷음 · 블록마다 기초 · SK · 고름 대기 · 고른 기초(바탕 {번: 로직}) · 별칭 장부 짝 = 기초까지만 · 맞춤 셈 전 409
- 여덟 파일 206 블록 = 기초 82 · SK 40 · 고름 대기 84 · SK 품 어긋남 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:51:04 +09:00
eomsangdon de2775d554 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-11-02 10:25:21 +09:00
eomsangdonandClaude Opus 5.5 81a7f8d801 feat(727-2 · 727-3): 둘째 기초 세부 줄 펼침 · 세부 덮개 · 뺀줄 · 출처 꼬리표 · 넣은 뒤 고른 옵션 SK 입력값 저장
- 서버 Sangeun_Doc: 더한줄 로직 = 덮개{세부 cid} · 뺀줄[세부 cid] 받음 · SK 입력값 = 바탕 입력 + 더한줄 기초 입력(727-3)
- 서버 Sangeun_Calc: 고친 둘째 기초 = 고친 기초 줄을 엔진으로 셈한 값묶음 줄(B09 굳힘도 같은 값) · 화면 셈 길만 펼침(세부 × 수량) · 뺀세부 · 바탕판 · 꼬리표(원문번호) · 저장 때 세부 원값 · 바탕판
- 화면 Cover_Model: 세부 줄 열쇠 add:#k/세부 cid · 품 = 화면 품 ÷ 수량 · 줄 빼기 · 되살림 · 기초 빼기 · 꼬리표 = 기초 둘 이상일 때만
- 화면 Sangeun_Detail: 옵션 칸 = 바탕 입력 + 더한줄 기초 입력 · 고르면 SK 입력값(열린입력만 시험 값) · M01 개정 알림에 둘째 기초 · [기초 빼기]
- 공용 상세 LogicLab_Ho: 줄 덧칸 꼬리표 배지(칸 폭 고정 · 말줄임 + 풍선 · M01 화면은 꼬리표 없음 = 전후 같음)
- 이음: 둘째 기초 수량 = 그 기초 기준단위 몇 개(처음 1) — 단위 · 배는 728
- 새 시험 test_727_sk_base_add(세부 둘 0.023 · 0.01546 · 기초 둘 계 = 서버 셈 · 덮개 · 뺀줄 · 굳힘 같은 값 · 암석작업 옵션 5,346.93) · 기존 SK 넷 금액 전후 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:24:50 +09:00
eomsangdonandClaude Opus 5.5 76ac1c499e feat(727-1): 고르기 창 기초 = 로직마다 한 줄 · [기본 로직 선택] 단추 걷음 · 첫 기초 = 바탕
- 기준 목록 옵션 줄(바꿈) 뺀 바탕 줄만 · 규격 칸 = 「옵션 N」(새 순수 함수 logic/M02_Find_Heads.ts) — 찾아 넣기 창 · 옛 창(Labor_Pick) 같이 · 옵션 줄 골라도 옵션 버리던 흠 걷음 · 서버 목록 그대로
- 단가산출 상세 [항목 추가] 기초 = 바탕 비었으면 바탕(옛 단추 동작 · 단위 비거나 식이면 기초 결과단위) · logicOnly 걷음 · locale M02_SangeunPickBase 지움
- 낡은 시험 test_721_find_pick 721-1 · 721-2 = 단추 걷음 · logicOnly 없음으로 고침 · 새 시험 test_727_sk_base_add
- 기초 줄 1,461(옛 10,053) · GF000099 한 줄 「옵션 1」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:14:07 +09:00
eomsangdon 1001676e5e Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-02 10:09:33 +09:00
eomsangdonandClaude Sonnet 5.5 54f06c8b88 feat(726-1): 화면 글 「단가산출 로직」 → 「단가산출 기초」 · 「바탕 M01 로직」 → 「바탕 단가산출 기초」
- 13 파일 화면 글 20 곳 + 바탕 3 곳 · 서버 까닭 글 M02_Labor_Doc 포함 · 열쇠 · 저장 꼴 · API 그대로
- 낡은 시험 고침: test_m02_item_list 표 머리 글 · ui_checks 넷(M01 제목 일치 글)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 10:08:27 +09:00
eomsangdonandClaude Opus 5.5 c201a1269d feat(728-1): SK 단위 바꿈 환산 순수 함수 · 파이썬 · TS 짝 · 거울 시험
- master_unit.swap · split — 같은 성격 = 배 자동 · 부피 ↔ 무게 = 단위중량 · 그 밖 = 사용자 계수 · 계수 없으면 배 없음 + 까닭 글
- A00_Common/master_unit_swap.ts swapUnit — 같은 답(단위 표 · 별칭 · 분모 읽기 옮김)
- 지킴 시험 test_728_1_unit_swap.py(거울 20 사례)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:08:02 +09:00
eomsangdonandClaude Opus 5.5 638d3f446d knowledge(요구): 요구서 내역 19 · 분석 둘 — 단가산출 기초 · M02 단가산출 항목 추가 · 단위 환산 · 실무문서 연결(PLAN 726~729)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 08:58:48 +09:00
eomsangdonandClaude Opus 5.5 6df8f93d91 knowledge(요구): 내역 20 원문 기록 — 기초 넣는 꼴 · 단위 환산 · 다른 단가산출 · 연결 누락 답
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 08:56:43 +09:00
eomsangdonandClaude Opus 5.5 52be8a3c07 knowledge(요구): 내역 19 원문 기록 — 단가산출 기초 이름 · M02 단가산출 항목 추가 기준단위 · 옵션 목록 · 기본 로직 선택 걷음 · 실무문서 연결 점검
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 07:17:39 +09:00
eomsangdonandClaude Opus 5.5 690726a387 knowledge(계획): PLAN 645 이관 — 데스크탑 검증 프로젝트 둘 새 지표면(분류 + implicit) 다시 확정 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 07:05:20 +09:00
eomsangdonandClaude Opus 5.5 5639d651e3 knowledge(요구): 요구 564 원문 기록 — 데스크탑 검증 프로젝트 새 지표면
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 06:44:35 +09:00
eomsangdonandClaude Opus 5.5 d2b3f6eec8 knowledge(요구): 요구 562 · 563 원문 기록 — 전역 깃 싱크 · 측구터파기 원문 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 06:35:58 +09:00
eomsangdon da8593e17e Merge remote-tracking branch 'origin/main_desktop_1' into sub_laptop_8 2026-11-02 06:26:33 +09:00
eomsangdon b737078ac2 chore(마스터): 데스크탑 검증 사용자 화면 편집 - 구조물 조합 item3_3(면적 · 노임 · 구조물) 11-01 13:20 2026-11-02 06:25:47 +09:00
eomsangdon c9bef8ce9f chore(데스크탑): 자동 커밋 346a70d9b 에 쓸려 든 사용자 config 둘 · Orca 끌어놓기 그림 둘을 추적에서 뺌 - 파일은 그대로 2026-11-02 06:25:46 +09:00
eomsangdonandClaude Opus 5.5 e3053134f4 knowledge(계획): PLAN 640~644 · 720~725 이관 — 라이다 지표면 · 내역 16 · 17 · 브레인 화면 검증 흠 모두 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:51:03 +09:00
eomsangdon bf95ee6341 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-11-02 00:50:10 +09:00
eomsangdonandClaude Sonnet 5.5 6a429560fb fix(725-6): 바탕 없는 단가산출 공통 옵션 칸(더한줄 로직 입력 모음) · 상세 못 셈 줄 금액 「—」 + 까닭 풍선
- 바탕 없는 SK = 더한줄 로직 입력을 모아(같은 이름 하나) 공통 옵션 칸으로 보임 · 값 자리 = 시험 입력 그대로 · 바탕 있으면 지금 그대로
- 입력 바꾸면 SK 셈 바로(바탕 없음 = 시험 계산 단추 없음) · 셈 때 SK 줄 입력 아닌 이름은 거름(공통 옵션 지역이 SK 줄에서 「모르는 입력」으로 멈춤)
- 공용 상세 줄 금액 칸 = 까닭 있는 줄만 「—」 + 풍선(M01 줄은 까닭 없음 = 전후 같음)
- 시험 test_725_1 에 바탕 없음 지역 서울 5,297.37 · 화면 짜임 단언 더함

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 00:49:31 +09:00
eomsangdonandClaude Sonnet 5.5 05234f1f6a test(725-5): M02 낡은 시험 8 파일 고침 - 소스 글·시간 표기 변화를 새 글로 따라감 (코드 안 고침)
- test_m02_item_list: 헬퍼 가짜 모듈에 진짜 common_util_time 연결 · TZ=Asia/Seoul 고정 · 서버 수정일 = UTC Z 20자(593-1)
- test_m02_hopyo_screen: 도면 양식 칸이 조건 없이 놓임(720-4) - 찾는 글 drawing ? 를 drawing, 로
- test_m02_layer_screen: const drawing = opt.isSystem 를 조건 없는 section(L("M02_KindDrawing")) 글로
- test_m02_practice: 차례 확인 글 drawing ? 를 drawing, 로
- test_m02_structure_def_screen: 지난 판 저장일 = UTC Z 초 단위(593-1) - 길이 16 을 꼴 확인으로
- test_m02_structure_option_groups: 옵션 줄은 716-3 에서 좌측 칸을 떠남 - 있다는 단언을 없다는 단언으로
- test_m02_structure_options: 같은 까닭 - 옵션 줄 단언을 없음으로, 구조물 조합 → 도면 양식 → 실무문서 차례만 확인
- test_m02_df_convert: 옛 사본 폴더가 낡아 어긋남 - 정본과 다르면 다시 복사 · 전수 대조는 열 줄에 하나(약 1분, AISLO_DF_FULL=1 이면 전수 약 12분) · 일꾼 동시 Node 호출로 드문 실패는 혼자 한 번 더

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 00:47:00 +09:00
eomsangdon 79c48ee480 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1
# Conflicts:
#	M02_MasterTemplete/M02_MasterTemplete_Sangeun_Detail.ts
#	resources/tester/test_717_4_sangeun_pick.py
2026-11-02 00:15:31 +09:00
eomsangdonandClaude Opus 5.5 d33706ceea fix(725-1·725-2): 단가산출 [항목 추가] 못 셈 줄 = 그 줄만 금액 없음 · 나머지 셈 계속
- 더한줄 로직 입력 = 줄 값 › 공통 옵션(바탕 입력값 · 화면 시험 입력) › 로직 기본값(M01 견본) 차례로 채움 — 셈 때 채워 문서엔 안 적음(공통 옵션 바꾸면 따라감)
- 그래도 빈 입력 · 수량 없는 일위대가(723-1) · 못 셈 참조 = 0 값묶음 줄(차례 그대로) + 줄 까닭 · 답 `빠진줄` · 계 = 나머지 합 · 줄 남아 빼기 됨
- 쓰는 자리(`unit` · 층 목록 기준금액 · 일위대가 펼침) = 빠진 줄 있으면 통째 기준 없음(부분 값 안 넘김)
- 화면 = 띠 「못 셈 줄 — 이름」 · 계산서 금액 — · M01 strict 셈 · Pick.ts 그대로
- 낡은 시험 고침: 715 store · 717-4 = 723-1 뒤 안 줄 수량을 알약(given)으로 · 719-2 = 수량 없음이 통째 까닭 아닌 빠진줄로
- 새 지킴 시험 test_725_1_sangeun_add_inputs(임시 storage)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:04:10 +09:00
eomsangdon 48b73b1f9f Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-02 00:03:41 +09:00
eomsangdonandClaude Sonnet 5.5 e526840671 test(723-1): 단가산출 고르기 시험 = 일위대가 안 줄 수량 저장 안 함 꼴로
723-1 · 내역 17-1 로 일위대가(SC) 안 줄 수량은 저장하지 않음(null).
test_list_amount_and_labor_line_holds_upper_sk 가 옛 꼴(SC 안 줄 손 3 저장)을 믿어 빨강.
- 저장본 안 줄 수량 null · 참조 = 회사 SK 그대로
- 금액: 바깥이 SK 를 손 3 으로 바로 참조 = SK 1 단위 × 3 · SC 를 손 1 로 담고 안 줄 미연결 = 0 · 안 줄 알약에 3 을 이으면 = SK 1 단위 × 3

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 00:02:56 +09:00
eomsangdonandClaude Sonnet 5.5 b1cba8ed14 fix(725-3·725-4): 바탕 없는 SK 거짓 M01 개정 막음 · 단가산출로도 저장 뒤 나무 다시 읽음 · 더한 줄 단가 찾아 옴
- 725-3 Sangeun_Detail — 개정 판정(배지 · 띠)은 바탕이 있을 때만(바탕 없는 SK 지문은 이름 · 단위 따라 바뀜)
- 725-4 Labor_RowModal — 단가산출로도 저장 성공 뒤 서고 목록 알림(HOPYO_BUILT) · 단가산출 나무 바로 갱신
- 725-4 LogicLab_Modal — prices 에 없는 요소(M02 더한 석공 · 합판)도 키로 찾아 단가 보임(lookup 하나로 합침)
- 시험 test_717_2b 에 725 단언 하나 더함

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 00:00:45 +09:00
eomsangdon 0001ef818c Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_4 2026-11-01 23:20:26 +09:00
eomsangdonandClaude Opus 5.5 0ec0b68492 fix(642): 점진 TIN 처짐 칸 씨앗 묶음 걷음 · 면 위 판정 0.12 m — 검사점 RMS 1.008 → 0.496 m
- 굵게 → 가늘게 다짐에서 「간격 절반보다 높이 뜬 칸 제외」 묶음을 걷음 — 급한 볼록 비탈이 계속 처져 implicit 검사점 RMS 가 CSF(0.587)보다 나빴음
- 마지막 판정 면 위 문턱 0.2 → 0.12 m — 덤불 점을 덜 받음
- 용화(분류 숨김) 업체 분류 대비 맞음 83.4 · 놓침 16.6 · 잘못 넣음 11.8 % · implicit 검사점 RMS 0.496 m · 4,900 만 점 205 초 · +1.2 GB

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 23:19:44 +09:00
eomsangdon 1dbae93408 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_5' into sub_laptop_1 2026-11-01 23:17:34 +09:00
eomsangdon b84ee6f9c1 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-01 23:16:12 +09:00
eomsangdonandClaude Opus 5.5 82e6e8584b feat(724-1): M02 단가산출 상세 · 일위대가 줄 모달 = 722 공용 상세(LogicLab_Ho) — 줄 모달 그 자리 고침 → M02 덮개 · 옛 고침 표 둘 · 이름 같은 모델 파일 걷음
- SK 셈 답 → 공용 상세 줄(logic/M02_Sangeun_Cover_Model.hoView · 정체 차례 · 바탕 줄 그대로 · 더한줄) · 고침 고리 HoEdit → setLineCover(덮개 · 더한줄 · 열린입력 = 배율)
- HoEdit 덧칸 more(줄 빼기 · 별도계상) · M02 = 덧줄 율 고침 · 꼬리표 「변경 항목」 · 뺀 줄 띠 = Sangeun_Cover.removedStrip
- 시험 계산 · 텍스트 수식 = 바탕 로직 + 덮개 품 · 단가 사본(applyCovers) · 뺀 바탕 줄 글 거름(dropText — 같은 이름 더한줄이 안 받음)
- 717-3 openRowSangeun = 줄 모드 상세(mountRowSk) 얇은 모달(Labor_Detail 부름 그대로) · skCoverTable · coverTable · 뿌리 M02_Sangeun_Cover_Model.ts 지움(Labor_Api import 한 줄)
- 낡은 시험: test_717_2b(setQty · setPrice → setLineCover · hoView · dropText) · test_717_3(모델 경로 · setCover · priceKey · sheetGroups 걷힘 → cleanPatch · 화면 이음 새 꼴)
- 안내 글(M02_SangeunBaseViews) = 덮개 사본 반영

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 23:15:16 +09:00
eomsangdonandClaude Opus 5.5 773544e5cb feat(642): 분류 없을 때 확정 기본 · B04 처음 고름 = ptd(점진 TIN) — 이음 몫
- SURFACE_CONFIRM_DEFAULT_FILTER csf → ptd · B04 DEFAULT_FILTER csf → ptd(확정본 있으면 확정 필터가 먼저 = 분류 LAS 는 classification 그대로)
- Engine_Ground resolve_auto_source_filters 설명 글 csf → ptd
- 낡은 시험 고침 — test_640_lidar_default · test_637_lidar 의 자동 필터 · 지워지는 모델 이름 csf → ptd(요구 559 사용자 확정)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 23:08:18 +09:00
eomsangdon 31b8131b50 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_5 2026-11-01 23:05:20 +09:00
eomsangdon 2874c0fab2 Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-11-01 23:05:18 +09:00
eomsangdon 05044fc3bd Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-01 23:03:44 +09:00
eomsangdonandClaude Opus 5.5 3a29e0480f feat(642): 점진 TIN(PTD) 내장 지면 필터 — 분류 없는 LAS 기본 필터 ptd · 화면 이름 「점진 TIN」
- 새 B04_PreProcess_Engine_Filter_PTD.py — 0.5 m 칸 최저 후보 · 시작 간격(기본 10 m) 칸 최저 씨앗 · 자료 가장자리 1/4 간격 씨앗 · 삼각형마다 문턱(장면 셋 평지 3°·0.3 m / 완경사 6°·0.5 m / 급경사 10°·0.7 m · 기본 급경사) 안 가장 가까운 후보를 더함 · 굵게 → 가늘게 다짐(후보가 모두 면 위로 뜬 칸에 반 간격 씨앗 · 2.5 m 까지) · 외딴 가시 · 잡점 꼭짓점 걷음 · 마지막 판정 = 면 아래 최대 거리 · 면 위 0.2 m · numpy · scipy 만 · 4,900 만 점 덩이 처리
- 삼각형 찾기 · 무게 직접 셈 — scipy find_simplex · transform 이 이 PC 에서 BLAS 스레드 다툼으로 수십 배 느림
- 필터 목록 ptd 한 줄(Engine_Ground · SURFACE_MODEL_SOURCE_FILTERS · 표본기 SurfaceFilter · B04 드롭다운) · SURFACE_AUTO_FALLBACK_FILTER = ptd(요구 559) · 설정 SURFACE_PTD_SCENE · SURFACE_PTD_SEED_SPACING_M
- 용화(분류 숨김) 업체 분류 대비 맞음 · 놓침 · 잘못 넣음 = CSF 55.1 · 44.9 · 12.2 → PTD 88.6 · 11.4 · 12.0 % · 급경사 놓침 60.9 → 19.3 % · 180 초 · +1.2 GB
- 새 지킴 시험 test_642_ptd_filter(합성 비탈 + 나무 · 급경사 놓침 상한)
- 낡은 시험 고침 test_b04_ground_filters — 분류 없을 때 자동 필터가 csf → ptd(요구 559 사용자 확정)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 23:02:31 +09:00
eomsangdonandClaude Opus 5.5 6db98d6b14 feat(643-1): B04 implicit 등고선 = 굳힌 격자 그대로 — 등고선 · 종단 · 횡단 같은 면
- implicit 등고선이 RBF 를 다시 세던 길 대신 implicit_{필터}_grid.npz 를 바로 씀(같은 dtm 없을 때만 옛 길)
- 용화(검증_sub4) WF1 다시 = classification + implicit 자동 확정 · 등고선 꼭짓점 − 표본 면 최대 3 mm(전 1.28 m) · 등고선 16.7 s
- 지킴 시험 test_641_1 에 등고선 = 표본 면 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 22:44:40 +09:00
eomsangdonandClaude Sonnet 5.5 5bd2df81a4 test(716-3): M02 왼쪽 칸 차례 시험 갱신 - 도면 양식 칸이 720-4 로 조건 없이 놓여 찾는 글 drawing ? 를 drawing, 로 교체
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 22:32:31 +09:00
eomsangdon c42c454c00 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-01 22:30:06 +09:00
eomsangdonandClaude Opus 5.5 8cbbe41d99 feat(723-3): 일위대가 줄 모달 = 단가산출 상세 줄 모드(보기 넷) — [적용] = 그 줄 덮개 + 옵션 · 단가산출로도 저장(기본 꺼짐)
- 새 `Labor_RowSk.ts` = 줄 → 이름 없는 SK 문서(lineDoc · 바탕 줄 정체) → 717-2b `mountSangeunDetail` 그대로 얹음 ·
  take = 패치 {덮개, 뺀줄, 더한줄}(M02 변경 항목 · M01 안 바뀜) + 옵션(손댄 값만 · 자동값 안 남김)
- `Labor_RowModal.ts` = 717-3 두 보기 갈음 · [적용] 뒤 닫힘 · [단가산출로도 저장] = 개인 층 새 SK(from-line)
- `Sangeun_Detail.ts` handle 에 getValues 한 줄(브레인 허락 · 724 공용 부품이 살림)
- `Labor_Table.ts` = 우클릭 메뉴 두 그림 뒤 엶(칸 초점 밀림에 닫히던 것) · 원문 패널 처음 접힘(표 넓게) · 머리 단추 묶음
- `Hopyo_Relink._total` = 엑셀 수량(본뜬.실무수량)으로 — 705-2 다시 잇기 전 · 후 금액 되살림(723-1 번짐)
- 새 지킴 시험 test_723_labor_table(저장 수량 걷음 · 이은 알약 · 안 줄 알약 · 화면 이음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 22:27:51 +09:00
eomsangdon a94cd33865 Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-11-01 22:26:15 +09:00
eomsangdonandClaude Opus 5.5 9633e97c3c feat(723-4): 723-1 뒤 B09 일위대가 수량 연결 마무리 — 알약 값 쓴 SC 만 덮개 나눔 · SK 속 SC · 공사 불러오기 수량 · 비고 경고
- Labor_Source = 덮개 열쇠 `C:참조|S:사본` 은 알약 값 쓴 SC 블록만(빈 수량 · 옛 손 = `C:참조` 그대로 = 703-1 지난 공사 가져옴 꼴) ·
  SK 속 SC 안 줄 수량 없음 = 엔진엔 0 값묶음(`_Loose`) · 그 줄 = 사전조합 블록(이 공사 입력) — SK 통째 빈칸 안 됨
- Bill_Hopyo(707-7 공사 통째 불러오기) = 이 공사 엑셀 수량을 굳힌 손으로 남김(723-1 `with_pills` 수량 걷기는 M02 저장용)
- Daega_Personal 원천 = 블록 origin 참조(덮개 열쇠 꼬리 무관) · 내 자료집 저장 알림 글 = 줄 수량 저장 안 함
- 비고(펼침 · 일위대가표) = 수량 없는 SC 안 줄은 「산근 n호표 · 수량 없음 — 이 공사에서 입력」(`noteOf`)
- 낡은 시험 고침(사유 = 723-1 SC 안 줄 수량 저장 안 함 · 이 공사 입력 · 알약으로 이음):
  test_204_2(서고 SC 수량 없음 · ask) · test_703(자료집 저장 손 걷힘) · test_719_1(서고 사본 = 알약 값으로 같은 값 · 없으면 수량 없음) ·
  test_719_2(SK 속 SC = 빈 줄 → 이 공사 입력 1 = M02 알약 1 금액) · test_702_3(서고 SC 빈 줄 3 → 입력 뒤 금액)
- test_723_4 = 구조물 사본 면적 알약 굳힘 · 설계 연동 · 알약 안 이음 = 빈 줄 시험 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 22:25:30 +09:00
eomsangdonandClaude Sonnet 5.5 23d8e2d7a2 fix(720-4): 도면 양식 칸 = 누구나 늘 보임(읽기 · 고르기) · 새로 · 본떠 · 지우기 단추만 시스템 관리자 · 실무문서 그대로
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 21:59:18 +09:00
eomsangdonandClaude Opus 5.5 6490b999e3 feat(723-2): M02 일위대가 상세 = 일위대가표만 — 수량 칸 휘발 임시 입력 · 우클릭(위로 · 아래로 · 별칭 · 삭제) · 줄 누름 모달
- 새 `Labor_Table.ts` = 서고(사전조합) 상세 — 정보 칸 · 일위대가표(laborBlock 격자) · 원문 패널 · 머리 [로직 추가] [자재 추가]
  · 수량 칸 = 확인용 임시 입력(저장 · dirty 없음 · 짝 셈 settle 로 금액 미리 보기) · 「빈 값 n」 = 비고 붉게
  · 붙여넣기 엑셀 수량 = 임시 칸으로 · 옛 문서 손 수량은 읽을 때 걷음(723-1) · lib.fixed(721-2 인력 · 재료 고정 줄)
- 새 `Labor_RowModal.ts` = 줄 누름 모달(옵션 · 메모 · 켜쌓기율 · 로직 자재 바꾸기 · 세 칸 · 단가산출 보기) · 자재 줄 모달(세 칸)
- `Labor_Detail.ts` = mode precombo 면 Labor_Table 로 · 서고 갈래 걷음(679 → 649 줄)
- 낡은 시험 고침: test_175 화면 글 대조(서고 갈래가 Labor_Table · RowModal 로 옮김)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:58:10 +09:00
eomsangdon 2fb57be255 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_6 2026-11-01 21:56:53 +09:00
eomsangdon 6609192ef4 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_6' into sub_laptop_8 2026-11-01 21:55:23 +09:00
eomsangdon 43fdd37ab5 Merge remote-tracking branch 'origin/dev' into sub_laptop_8 2026-11-01 21:55:20 +09:00
eomsangdon 6fbb1c3f65 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-01 21:55:18 +09:00
eomsangdonandClaude Opus 5.5 cf41e303d0 feat(723-4): B09 사본 일위대가(SC) 안 줄 수량 = 굳힌 손 → 구조물 사본 면적 알약 값 → 없음(빈 줄 · 이 공사에서 입력)
- Labor_Source.precombo = 빈 손 1 로 셈 걷음 · 안 줄 · 자재 수량 = 굳힌 옛 손 → 쓰는 구조물 사본 면적 알약 값
  (723-1 「참조 id#차례」 · 「참조 id#자재|코드」 · 짝 = M02_Labor_Expand._qty 차례) → 없음 = qty null · `ask`
- 알약 · 없음 쓴 SC 블록 덮개 열쇠 = `C:참조|S:사본` · `C:참조|L:서고`(쓰는 곳별 · 옛 손뿐 = `C:참조` 그대로)
- 셈 짝(Calc_Labor .py ↔ .ts) = `ask` 줄 수량 비면 비고 · 연결 없음 「수량 없음 — 이 공사에서 입력」 · 702 덮개 qty 입력 뒤 금액 · 글 없음
- 지킴 시험 test_723_4_b09_sc_qty(경고 · 입력 금액 · 알약 굳힘 · 옛 손 · 거울)
- 기존 공사 금액 전후 같음(검증_랩탑메인 · 검증_sub8 B09 셈 답 통째 같음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:55:12 +09:00
eomsangdon d301ceb086 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-01 21:51:57 +09:00
eomsangdonandClaude Opus 5.5 7d007e4feb feat(723-1): 일위대가(사전조합) 안 줄 수량 저장 안 함 — 구조물 조합 안 줄 알약 연결 · 불러오기 수량 = 본뜬 출처 기록
- 저장(`M02_Labor_Ref.no_qty`) = 안 줄 · 자재 수량 늘 null(비율 고정 줄 율만) · 기본 {손: 1} 걷음 · 굳힌 사본도 걷음
- 펼침(`Expand`) = 안 줄 수량 = 쓰는 쪽 given 알약 「참조|층|주인|키#차례」 · 「…#자재|코드」 · 층 원본 옛 손 안 씀 ·
  굳힌 사본(프로젝트) 손 · 비율 줄 율만 · 없으면 「수량 없음」(1 아님 · 그 일위대가 기준 없음)
- 구조물 조합 알약(`pills`) = 참조 줄 + 안 줄 알약(`inner_pills`) — 면적산출 칸에 이음
- 불러오기(`Hopyo_Build`) = 수량 안 들임 · 엑셀 수량 = `본뜬.실무수량`(셈 안 씀 · 엑셀 대조만 · 하위 호표까지)
- B08 `_ref_mats` = 안 줄 수량 = 굳힌 손 · 아니면 사본 면적 알약 값
- 낡은 시험 고침(사유 = 수량 저장 안 함 · 이은 값으로 셈): test_m02_labor_ref · test_175_precombo_mats ·
  test_715_3_labor_patch · test_m02_hopyo_build · test_m02_hopyo_flow · test_160_3(굳힌 옛 사본 꼴 손 채움)
- 남김: B09 시험 5(test_204_2 · test_703 · test_719_1 · test_719_2) = sub8 723-4 가 B09 코드와 함께

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:51:27 +09:00
eomsangdonandClaude Opus 5.5 e9106ae4d6 fix(720-2): 단가산출 · 일위대가 층 줄 「+」 = 구조물 조합과 같은 자리(줄 오른쪽 끝 m02-tree__tools)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:47:26 +09:00
eomsangdon 2cc20c6a60 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_6' into sub_laptop_7 2026-11-01 21:46:48 +09:00
eomsangdon e9a4c05ce4 Merge remote-tracking branch 'origin/dev' into sub_laptop_7 2026-11-01 21:46:46 +09:00
eomsangdon 168e34f2f9 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-11-01 21:45:51 +09:00
eomsangdonandClaude Opus 5.5 0e24c7a2aa feat(721-2): M02 찾아 넣기 창 — 탭 M01 전체(로직 · 인력 · 재료) · 단가산출 · 일위대가 · 찾기 칸 하나 · 거름 값 = 구분 · 출처 열
- 새 파일 M02_MasterTemplete_Find_Pick.ts — 있는 길 넷 이음(기준 목록 · 로직 · 층 서고 목록 · /logic-lib/pick 인력 job · 재료 price 서버 찾기) · 기계 · 실무 호표 뺌
- 부르는 곳 셋: 단가산출 [기본 로직 선택](로직 줄만) · [항목 추가](더한줄 인력 · 재료 = 요소 키 LB · MT 수량 1) · 일위대가 [로직 추가](Labor_Pick 이 lib.sangeun 이면 새 창)
- 일위대가 인력 · 재료 = 고정 줄 이음 꼴 fixedOf · lib.fixed 받는 칸은 723(sub1) — 그 전엔 회색 막힘
- 옛 창(B09 둘 · 불러오기 둘 · 프로젝트 층) 그대로 · 옛 창의 단가산출 탭 코드 걷음(부르는 곳 없음)
- 부품 상자: 데이터 표 흐린 줄 is-dim 더함
- 낡은 시험 test_717_4_sangeun_pick: 옛 창 단가산출 탭 · 재료 숨은 한 줄 글 대조 → 새 창 꼴로 고침
- 새 지킴 시험 test_721_find_pick(부르는 곳 · 옛 창 그대로 · 인력 · 재료 줄이 서버 셈에서 노임 · 자재 값)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:45:11 +09:00
eomsangdon 19849ab08a Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-01 21:41:26 +09:00
eomsangdonandClaude Opus 5.5 190789cdf7 feat(722-1·722-2): M01 단가산출 상세 개편 — 공용 상세 부품(하위 박스 · 계산식 · 박스 옵션) · 줄 모달 그 자리 고침(시험 값)
- 새 공용 단가산출 상세 `M01_MasterData_UI_LogicLab_Ho.ts` — 하위 박스 = 서버 묶음(인력 · 재료 · 경비(장비) · 하위) · 장비 · 하위 로직 = 한 줄 + 펼침(부른 로직 시간당 줄 · 몫 줄 · 다시 셈해도 펼친 채) · 「계산식」 열 = 값 든 식 + 알약 + 그 줄 입력 칩 · 이름 식 = 풍선 · 박스 제목 아래 옵션 고르기 · 어느 박스도 안 쓰는 입력 = 공통 옵션 · 맨 아래 비목 소계 셋 + 계
- 새 `M01_MasterData_UI_LogicLab_Cover.ts` — 줄 모달 「그 자리 고침」(수량 · 단가 · 로직 줄은 노 · 재 · 경) · M01 = 시험 값만(로직 줄 객체 열쇠 메모리 · 시험 계산 줄 사본에 손 단가 · 값묶음 · 수량 덮음 · 정본 · 요소 · 캐시 안 바뀜) · 고침 꼴 = M02 덮개 칸(724 고침 고리 `HoEdit`)
- `LogicLab_Detail.ts` 단가산출 상세 = 공용 부품(호표 묶음 표 코드 옮김 · 551 → 252 줄) · `LogicLab.ts` 시험 계산 줄 = trial.apply · dirty 에 시험 고침 · onTrial(줄 수 627 그대로)
- `calc_sheet.ts` varRow 내보냄 · 값 없으면 빈 첫 칸 · `LogicLab_Formula.ts` inputChip 내보냄 · `LogicLab_Modal.ts` 줄 모달 edit 칸
- 보기 고르기 넷 그대로(지우기는 사용자 확인 뒤)
- 지킴 시험 test_722_ho_detail(박스 묶기 · 고침 덮기 순수 함수 · 캐시 밖 · 부품 상자)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:39:17 +09:00
eomsangdonandClaude Opus 5.5 ace439ad04 feat(720-4): 구조물 조합 층 단추 = 제자리 바꿈(해시로 페이지 통째 다시 세움 없음) · 도면 양식 · 실무문서 칸 = 시스템 관리자면 늘
- 페이지: 층 고름(resolve) · 층 맞춤(settleLayer) · 띠 · 나무 · 층 줄 갈아 끼움(build) 한 길 · 주소 replaceState · 새 층 구조물 표
- 편집 칸 권한 = 함수(UI_Main isAdmin) · 「새 항목」 = 고른 것 권한으로 가림 · 불러오기 장부 = 열 때 층 · 권한(Hopyo_Side)
- 왼쪽 칸 펼침 · 단가산출 · 일위대가 층 그대로
- 낡은 시험(층 단추 해시 이동 · 사람 나무 · 도면 칸 층 묶음 · 새 항목 · 머리 고침 · 불러오기 권한 · 실무문서 차례 · home) 새 꼴로 — test_m02_practice 도면 차례 글은 이미 낡아 실패하던 것

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:37:10 +09:00
eomsangdon 956269964c Merge remote-tracking branches 'origin/main_laptop_1', 'origin/sub_laptop_3' and 'origin/sub_laptop_6' into sub_laptop_4 2026-11-01 21:29:42 +09:00
eomsangdon ca42faf873 Merge remote-tracking branch 'origin/dev' into sub_laptop_4 2026-11-01 21:29:40 +09:00
eomsangdonandClaude Opus 5.5 b85add0a51 feat(640-3): 외곽 틈 닫기 3 m — 성긴 숲 띠까지 스캔 범위 살림
- SURFACE_FOOTPRINT_GAP_CLOSE_M 1.0 → 3.0 · 「가장 큰 덩이만」 그대로
- 설정 지문에 들어가 다음 짓기 때 모든 표현 · 등고선 · 유효 범위가 새 외곽으로 다시 지어짐(기존 확정 파일 · DB 는 그대로)
- 지킴 시험 test_640_lidar_default 틈 닫기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:27:24 +09:00
eomsangdon 73d5084037 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-01 21:26:02 +09:00
eomsangdonandClaude Opus 5.5 a0c2db507f feat(720-3): 단가산출 · 일위대가 나무 문서 줄 [이름 바꾸기] — 읽고 이름만 갈아 저장 · 저장 뒤 상세 머리 제목 새 이름
- 열린 문서면 저장 안 한 고침 물음 뒤 새 이름으로 다시 엶 · 서버 새 길 없음
- 새 지킴 시험 test_720_m02_folder_layer(임시 storage 이름 바꾸기 왕복 · 화면 글 대조)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:25:12 +09:00
eomsangdonandClaude Opus 5.5 437333c7af feat(640-2): implicit 점 수 = 지면점 비율 · 격자 칸 뽑기 · 설정 지문 바뀌면 다시 지음
- SURFACE_IMPLICIT_POINT_RATIO 0.175 · _MIN_POINTS 5 만 · _MAX_POINTS 200 만(옛 _MAX_POINTS_PER_TILE 걷음)
- TerrainContext.grid_sample — 칸 = √(외곽 넓이 / 점 수) · 씨앗 558 · 칸마다 하나 · 모자라면 무작위 채움(sample() 그대로)
- 짓기 캐시 흠 — manifest 항목 완료일 때만 재사용(지문 바뀐 뒤 implicit · nurbs · meshfree 옛 파일 재사용 막음)
- 지킴 시험 test_640_lidar_default 점 수 · 격자 뽑기 · 지문 다시 지음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:25:08 +09:00
eomsangdonandClaude Opus 5.5 b3e62c7ae4 feat(641-2): B07 유역 음영 · D03 지형 — implicit 확정이어도 평활 DTM 읽음 · 지킴 시험
- 읽는 자리 한 줄씩(계산 코드 그대로) · dtm 확정은 전과 같음
- test_641_1_implicit_grid: 굳힌 격자 = RBF · 다시 셈 · 서버 · Node · B06 · 브라우저 같은 파일 · B07 · D03 전후

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:22:09 +09:00
eomsangdonandClaude Opus 5.5 f912efd36a feat(641-1): implicit 굳힌 격자 — 확정 지표면 B05 · B06 화면 일치
- implicit_{필터}_grid.npz = 같은 필터 dtm 축 · 유효 칸 · 유효 칸만 RBF · 밖 = dtm 값 · implicit · dtm 보다 옛것이면 다시 셈(파생 저장본)
- 서버 표본기 implicit = 굳힌 격자 DtmGridSampler(매번 RBF 다시 짓기 없앰) · Node 측점이 source 로 같은 격자
- B05 브라우저 격자 통로 · B06 서버 재계산 격자 = implicit 확정이면 같은 굳힌 격자(새 짝 없음 · 기존 heightAt 짝)
- 짓기 끝(build_implicit)에서 한 번 굳힘 · 못 지으면 처음 읽을 때

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:21:57 +09:00
eomsangdonandClaude Opus 5.5 194e1a9c39 feat(720-2): 최상위 폴더 만들기 — 폴더 · 문서 줄 메뉴 + 단가산출 · 일위대가 층 줄 「+」(구조물 조합과 같은 addFolder([]))
- 구조물 조합 나무 「〇〇로 복사…」 몸을 TreePanel_CopyTo 로 뗌(TreePanel 700 → 688 줄 · 동작 같음)
- 낡은 시험 test_m02_layer_screen(복사 대상 글 자리 = CopyTo) · test_m02_precombo_scope(층 줄 add 인자) 새 꼴로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:21:29 +09:00
eomsangdonandClaude Opus 5.5 366fd7a700 feat(640-1): 라이다 기본 고름 — 확정 implicit · 평활 끔 · 분류 덮임 조건 · implicit 기본 필터에만
- 확정 기본 표현 implicit · 평활 False · WF1 자동 짓기에 확정 표현 더함(SURFACE_AUTO_METHODS 는 dtm 그대로)
- 「분류 있음」 자동 고름 = 비율 0.2 % + 10 m 칸 덮임 50 % · 덮임 저장본(classification_coverage.json · 점군 안 풂)
- 분류 LAS 의 implicit 는 기본 필터에만 지음(csf 두 번 안 지음)
- B04 화면 확정 없을 때 기본 표현 implicit
- 지킴 시험 test_640_lidar_default

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:20:24 +09:00
eomsangdon 6ff98c9e96 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_1 2026-11-01 21:15:17 +09:00
eomsangdonandClaude Opus 5.5 0a86b4b060 knowledge(요구): 요구 561 원문 · PLAN 644(기존 라이다 프로젝트 영구저장소 · 새 기본 미리 지음) · 723-4(B09 일위대가 안 줄 수량 연결)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:15:14 +09:00
eomsangdonandClaude Opus 5.5 7621fff233 feat(720-1): 단가산출 · 일위대가 층 단추 = 저장 안 한 고침 물음 뒤 그 층 나무 + 상세 목록표
- 낡은 시험 test_m02_precombo_scope 층 단추 줄 글 대조를 새 꼴로(옛 go = 나무만)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:11:43 +09:00
eomsangdonandClaude Opus 5.5 931793d031 knowledge(요구): 요구 560 · 내역 18 원문 기록 — 밤 맡김(요구 반영 원문 대조 · 브레인 브라우저 검증 · 아침 아티팩트 보고서)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:11:12 +09:00
eomsangdonandClaude Opus 5.5 f7ebf1831f feat(721-1): M02 단가산출 상세 단추 이름 · 자리 — 「기본 로직 선택」을 「항목 추가」 왼쪽 · 「줄 더하기」 → 「항목 추가」
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:08:07 +09:00
eomsangdonandClaude Opus 5.5 887ca4a035 knowledge(요구): 요구서 내역16 · 558 사용자 확정 반영 — 일위대가 수량 연결(조사 먼저) · M02 고친 값 보관 · 점진 TIN 내장
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:03:50 +09:00
eomsangdonandClaude Opus 5.5 899c6a01ab knowledge(요구): 내역 17(일위대가 수량 = 저장 안 함 · 구조물 조합 알약 · 프로젝트 계산값 연결 · 만들 때 휘발 확인 · M02 고친 값 = M02 변경 항목) · 요구 559(CSF = 처음부터 점진 TIN 내장) 원문 기록
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:03:17 +09:00
eomsangdonandClaude Opus 5.5 4ba8ca793b knowledge(요구): 요구서 558(라이다 내장 개선) · 내역16(M02 정리 · 일위대가표 · M01 상세) · 조사 셋 · PLAN 640~643 · 720~724
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 20:55:56 +09:00
eomsangdonandClaude Opus 5.5 f189832ed5 knowledge(요구): 요구 558(라이다 내장 개선 · classification/csf + implicit · 점 수 비율 · 틈 닫기 3 m · B07 유역 · D03 안 건드림) · 내역 16(M02 폴더 · 층 · 단가산출 이름 · 단추 · 항목 추가 · 일위대가표만 · M01 상세 하위 박스 · 검색) 원문 기록
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 20:29:53 +09:00
eomsangdonandClaude Opus 5.5 a3d24ce738 knowledge(요구): 요구 557 implicit 코드 조사 — 쓰는 곳 갈림(일부 화면 DTM) · 누락 = 외곽 틈 닫기 1 m + 가장 큰 덩이 · 점 수가 품질 · 틈 닫기 3 m 면 덮임 65→94 %
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 19:44:57 +09:00
eomsangdonandClaude Opus 5.5 a1c33a2d9d knowledge(요구): 요구 557 implicit 시험 결과 — 20만 점이면 매끈 + 세부 지킴 · 누락 = 외곽 마스크(DJI 지면점 없는 띠 36 %) · 빈 띠 메우기는 정답 없음 · 외부 값과 비교 됨(폭)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 19:35:20 +09:00
eomsangdonandClaude Opus 5.5 f27587b823 knowledge(요구): 요구 557 덧 「조사부터」
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 19:12:21 +09:00
eomsangdonandClaude Opus 5.5 82356b3ee5 knowledge(요구): 요구 557 원문 기록 — 분류 있으면 implicit · 없으면 내장 csf + implicit 확인 · implicit 누락 처리 · 외부 라이브러리 값과 비교
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 19:10:39 +09:00
eomsangdonandClaude Opus 5.5 63f3a80029 knowledge(요구): 요구 556 조사 둘 — DJI Terra · Agisoft Metashape 지표면 단계 · 코어 QGIS 아님 · 용화.las = DJI L1(Terra 분류)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:40:41 +09:00
eomsangdonandClaude Opus 5.5 48fcbbc5c9 knowledge(요구): 요구 556 원문 기록 — 테스트 사용자 상용 프로그램(DJI · 러시아) 라이다 지표면 조사 · 코어가 QGIS 면 방향 바꿈
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:24:21 +09:00
eomsangdonandClaude Opus 5.5 c68535227d knowledge(요구): 요구 555 원문 기록 — 라이다 지표면 3단계 · 정합성 기준 물음 · M01/M02 화면 검증 미룸
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:17:58 +09:00
eomsangdonandClaude Opus 5.5 a66d2c2161 knowledge(계획): PLAN 709~719 이관 — 내역 12~15 끝(M01 단가산출 보기 · 엔진 바닥 · 원문 대조 · M02 단가산출 · 메뉴 · 불러오기 · 별칭 · 프로젝트 복사) · 넓은 시험 276 통과
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:46:43 +09:00
eomsangdon c9194dc84b test(716-4): 낡은 시험 고침 — 쓰는 일위대가 자료에 단위(㎡) 추가
715-3 줄 있는 일위대가 단위 빈칸 400 규칙과 이음 · 기능 흠 아님
2026-11-01 17:46:01 +09:00
eomsangdon 3e563609b1 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-01 17:40:18 +09:00
eomsangdonandClaude Opus 5.5 df31e51660 test(717-2b): 낡은 시험 test_좌측_고름_표시는_열린_것_한_줄뿐 지금 꼴로
- 716-3 이 M02 좌측 옵션 줄을 뺌 — paint 끝 줄이 options.clear() 에서 practice?.clear()(실무자료 판)로 바뀜 · 찾는 글을 지금 꼴로 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:34:37 +09:00
eomsangdonandClaude Opus 5.5 a1342124c6 feat(717-2b): SK 더한 재료 줄 = 기본 계 안(재료 소계 · 계에 더함) · 줄 「별도계상」 고름만 계 밖
- 서버 M02_Sangeun_Doc — 더한줄 재료 칸 별도계상(true 만 남김) · patched 가 더한 재료 줄 자재표시를 직접계산(기본) | 별도계상(칸 켬)으로 둠 · 원문 로직 줄 별도계상은 그대로
- 화면 — SK 상세 고침 표 더한 재료 줄 로직 칸에 「별도계상」 칩(켬 · 끔) · 순수 함수 setApart · 행 apartPick
- 지킴 시험 — test_715_sangeun_engine 더한 재료 계 안 · 별도계상 계 밖 · test_717_2b 칩 꼴

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:34:36 +09:00
eomsangdon 83b853c060 Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-11-01 17:29:27 +09:00
eomsangdonandClaude Opus 5.5 51f07499f5 feat(719-4 · 719-3): B09 단가산출근거 탭 = 공용 계산서 카드 · 이 공사 산근 덮개
- 719-4 근거 탭 = 산근 블록마다 카드 · 공용 calcSheetRows 묶음 꼴(인력 · 재료 · 경비(장비 한 줄 + 중기사용료 펼침 넉 줄) · 하위 = 산근 n호표 한 줄) · 서버 줄 덧칸 sheet{쓰는입력 · 기준단위 · 환산근거}(master_sheet_line 한 벌) · 블록 기준단위 · 입력 · 중간값 글은 고치기 펼침 때 · 옛 풀이표는 곁 창에만
- 719-3 산근 덮개 sg_cover {산근 키: {「n:이름」: {qty · 노 · 재 · 경 단가}}} · 셈 짝 B09_Estimation_Sangeun_Cover.py ↔ .ts(서버 풀이 뒤 덧씌움 · 하위 산근 이어짐 · % 덧줄 밑수 비례 · 총계 · 목록표 · 빈칸 목록 · raw 로 두 번 덮어도 같음 · 짝 잃음 sg_orphans 알림)
- 고침 표(702 꼴) = 품 · 단가 두 번 눌러 고침 · 고친 칸 색 · 원래 값 칸 · 「이 공사에서 고침」 꼬리표 · 우클릭 · 카드 단추 [단가산출 값으로] · 고친 뒤 보던 자리 지킴
- 저장 다듬기 sg_cover · [업데이트] 알림 고친 칸 수에 산근 칸 · 셈 길(compute_sheets) · 화면 짝(computeSheets) 둘 다 덮개
- 금액 전후 같음 — 덮개 없음 = 서버 풀이 그대로 · 덧칸 뺀 옛 풀이와 13 블록 같음 · 일위대가 합 검증_랩탑메인 13,662,560 · 검증_sub5 10,149,562 그대로
- 시험 새로 test_719_3_sangeun_cover(8 · 거울 · 대가 · 원가계산서 · 업데이트 뒤 지킴) · test_719_4_sangeun_sheet(2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:28:11 +09:00
eomsangdonandClaude Opus 5.5 04f3489481 merge(717-2b): origin/main_laptop_1 합침 — Sangeun_Cover.ts 같은 이름 새 파일 충돌(AA) 풂 · 두 표 한 파일
- 셈 길 calcSangeun 한 벌 — 답 꼴 제네릭(기본 717-2b SkAnswer · 717-3 모달은 SkCalc) · 시험 입력 칸(입력값) 그대로 · owner 는 시스템 층 밖만
- 717-3 일위대가 모달 표 = coverTable · sangeunFromLine 그대로 · 717-2b SK 상세 표 = skCoverTable(옛 coverTable 이름 바꿈 · 옵션 = SkCoverOptions)
- 모달 부르는 자리 셋에 <SkCalc> 만 더함
- 시험 test_717_2b · test_717_3 · 715 · 719 통과 · typecheck 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:27:50 +09:00
eomsangdon 27d88eaeb3 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_2 2026-11-01 17:24:45 +09:00
eomsangdonandClaude Opus 5.5 53bca68ea4 feat(717-2b): M02 단가산출 상세 · 고침 — M01 상세 부품(보기 넷)을 M02 길로 · 품 · 단가 칸 덮개 · 줄 더하기 · 빼기 · 바탕 M01 로직 고름
- 새 Sangeun_Detail(mountSangeunDetail) · Sangeun_Cover(고침 표 · 셈 길 최신 응답만) · logic/M02_Sangeun_Cover_Model(순수 꼴)
- 고친 칸 = 칸 색 + 바탕 값 곁 글 + 「단가산출에서 고침」 · M01 개정 배지(바탕판 · 원값) · 짝잃음 띠 · 별도계상 재료 = 계 밖 표기
- M01 buildLabDetail 덧칸 cover(단가산출 상세 · 인력·재료·경비 덩이 갈음) · 마스터 이동 단추 = 등록 없으면 숨김(hasMasterJump)
- Sangeun_View 자리표 · 옛 글 열쇠(준비 중 · 덮개 · 더한 줄 · 열린 입력) 걷음
- 시험 test_717_2b_sangeun_detail 3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:23:02 +09:00
eomsangdon 1a73413254 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-01 16:53:48 +09:00
eomsangdonandClaude Opus 5.5 c818dcae5e feat(717-3): 일위대가 줄 [단가산출 보기] 모달 — 인력·재료·경비 · 단가산출 상세 고침 = 그 일위대가 줄 덮개 · [단가산출로 저장]
- 줄 단추 = 로직 줄 · SK 참조 줄(Labor_Rows `sangeun`) · 프로젝트 복사본(셈 주입)엔 없음
- 모달(Labor_SangeunModal) = 일위대가 머리 「1 ㎡ 당」 · 이 줄 수량 · 1 단위 금액 · 보기 고르기(기본 인력·재료·경비 = 공용 계산서 calc_sheet · 상세 = 품 · 단가 칸 고침)
- 고친 줄 = 표 부품 「바뀜」 + 원래 값 곁 + 「이 일위대가에서 고침」 꼬리표 · [적용] = 줄 칸 패치(화면 캐시 · 저장은 [저장]) · 적용 안 하고 닫으면 물음
- [단가산출로 저장] = 내 개인 층 새 SK(from-line) · 자기 개인 층 일위대가면 그 줄을 새 SK 참조 줄로
- 셈 = 서버 /sangeun/calc(저장 안 함 · 최신 응답만) · 화면 문서 = 서버 as_doc 꼴(Sangeun_Cover_Model · 고름 · 자재표시 cid)
- 고침 표 · 셈 길 부름 = Sangeun_Cover(717-2b 도 씀) · normalizeLabor 가 줄 패치를 지킴(떨구면 [저장]에서 사라짐)
- 시험 test_717_3 5 통과 · 고친 대상 166 통과(1 실패 = test_175 B09 Store_Combo._PER · 전부터 · 719-2 몫) · typecheck 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:52:56 +09:00
eomsangdon 565f437d2e Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-01 16:48:09 +09:00
eomsangdonandClaude Opus 5.5 c0f3d21908 feat(713-3): M01 로직 목록 묶음 접기 — 같은 절 · 작업량 로직은 머리 줄 밑으로 접음(접기 순수 함수 logic/list_fold) · 찾는 중엔 펼침 · 「관련」 열 = 비고가 가리키는 로직 링크(계산 밖) · 서버 덧칸 인재경 줄 수(712-1 master_bundle) · 지킴 시험
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:47:28 +09:00
eomsangdon 54ab449a9c Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_2 2026-11-01 16:47:06 +09:00
eomsangdon 27caa818f1 Merge remote-tracking branch 'origin/dev' into sub_laptop_2 2026-11-01 16:47:04 +09:00
eomsangdon aced540f14 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-01 16:41:59 +09:00
eomsangdon 117d5e25b1 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-01 16:41:57 +09:00
eomsangdon 1a3d50be68 Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-01 16:41:56 +09:00
eomsangdon fa836163fa Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-11-01 16:41:54 +09:00
eomsangdonandClaude Opus 5.5 dcf476d504 feat(714-2): 장비 펼침 넉 줄 — 서버 펼침(714-1b · 부른 기계경비 로직 시간당 줄 조종원 · 주연료 · 잡재료비 · 기계손료 · 원/시간 계) 머리 글 + 넉 줄 → 그 장비 노 · 재 · 경 몫 식
- calc_sheet.ts SheetUnfold · 펼침 줄 · calc_sheet.css 펼침 글 들여씀
- 지킴 시험 test_714_2_calc_sheet 펼침 글 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:39:11 +09:00
eomsangdonandClaude Opus 5.5 328cb46d92 fix(719-2): 산근 결과단위 배 = 712-2 단위 한 벌(master_unit.per) — 받은 712-2 가 Store_Combo._PER 를 걷음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:38:24 +09:00
eomsangdonandClaude Opus 5.5 c60b87c111 feat(715-1b): 엔진 입력 덧칸 — 호표 줄 단가(손 단가) · 로직 줄 값묶음(노 · 재 · 경) · SK 고친 줄 셈 마무리
- master_pick — _priced 앞 「단가」 덧칸 = 손 단가(찾기 없음 · 출처 「손 단가」) · bundled(값묶음 셋 = 그것 · 일부 = 엔진 값 위 · 계 = 셋 합)
- master_formula — 로직 줄 묶음 부르는 줄 하나(mp.bundled) · 712-1 출력 묶음과 다른 이름
- M01 호표 5,266 줄에 덧칸 없음 · M01 1,469 로직 값 전후 같음
- 시험 test_715_sangeun_engine.py 다시 넣음(손 단가 · 3 % 다시 셈 · 값묶음 일부 · 참조 · 고정 더한줄)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:38:19 +09:00
eomsangdon 1b830c9e8b Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-01 16:37:26 +09:00
eomsangdon 1e6092fad8 Merge remote-tracking branch 'origin/dev' into sub_laptop_4 2026-11-01 16:37:08 +09:00
eomsangdonandClaude Opus 5.5 f6a4296517 fix(713-4): 정본 로직 원문 대조 정리(내역 15) — 조종원 시간 환산 = 하루 노임 × 1/8(품셈 1-1-3 · 질의회신 상여 · 휴지계수 뺌 · 기계경비 8) · 구조물터파기 1~6 = 7~18 꼴(작업량 로직 6 지움 · 관부설 가산 비고) · 플랜트 배관 13-1-1 쪽 표 다섯 · 로직 다섯 → 한 표 · 한 로직 · 기초다짐 13-3-1 셋 → 하나 · 방제 8-3 빠진 로직 넷 채움 · 713-1 확인 길로 저장 · 지킴 시험
낡은 시험 고침: test_masterdata_check 굴착기 · 암석 보정 · 노체포설 · 집게 기대값 = 조종원 1/8 꼴 값(노무비 × 0.6) · test_713_3_list 목록 수(1,461 · 묶음 307 · 작업량 21 · 터파기 작업량 지움으로 표본 GF000098)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:36:45 +09:00
eomsangdon ebc6ac25a9 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_3 2026-11-01 16:34:47 +09:00
eomsangdon 6479145d20 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-01 16:33:49 +09:00
eomsangdon 6a73a4efd5 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-01 16:33:47 +09:00
eomsangdon 63ac37759e Merge remote-tracking branches 'origin/main_laptop_1', 'origin/sub_laptop_1', 'origin/sub_laptop_2' and 'origin/sub_laptop_3' into sub_laptop_8 2026-11-01 16:33:31 +09:00
eomsangdon 0ed745e98c Merge remote-tracking branch 'origin/dev' into sub_laptop_8 2026-11-01 16:33:28 +09:00
eomsangdonandClaude Opus 5.5 6126f5ee20 fix(719-2): 업데이트 뒤 서고 사본 줄 연결 다시 이음 · 단가산출 블록 안내 글 되돌림 이름
- Shell: [업데이트]가 서고 사본을 다시 굳혀 블록 id 가 바뀌면 화면 캐시 줄 연결을 사본 폴더(lib)로 다시 이음(짝 Bill_Library.fix_keys) — 캐시가 옛 id 를 잡아 단가 빈칸 되던 것
- Bill_Fold: 단가산출 블록 안내 글 「우클릭 [단가산출 값으로]」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:33:17 +09:00
eomsangdonandClaude Opus 5.5 8c519c0ab2 feat(714-1b): 장비 줄 펼침 덧칸 — 부른 기계경비 로직 계산서 줄 · 시간당 소계 · 계 · 키 · 인자
- master_sheet_line 장비(기계경비) 줄에 「펼침」 = {키, 이름, 단위, 입력, 줄[비목 · 이름 · 글 · 금액], 소계, 계} · 못 셈 = 까닭
- 셈 결과(master_text.lines)를 그대로 실음 — 조종원 환산 같은 규칙을 여기 두지 않음
- annotate 가 files 를 받음(펼침 계산서) · M01 /text · M02 /logic-lib/sheet 같은 KEYS
- 시험 test_714_1_sheet_line 펼침 · 두 길 같은 수(JSON 수 규칙)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:32:57 +09:00
eomsangdonandClaude Opus 5.5 f8338194c6 refactor(717-3): 일위대가 상세 · 줄 표 나눔 — 자재목록 표(Labor_MatTable) · 서고 구분 고르기(Labor_LibMeta) 떼어 냄 · 동작 같음
- Labor_Rows 671 → 566 줄 · Labor_Detail 700 → 650 줄(717-3 모달 단추 자리)
- 낡은 시험 셋 고침: mat_marks · precombo_class = 옮긴 파일을 읽음 · hopyo_screen 의 Detail 「꼭 700 줄」 = 「700 줄 넘지 않음」(나눔으로 줄어듦)
- 시험 158 통과 · 1 실패(test_175 B09 겹 대가 = Store_Combo._PER 없음 · 나눔 전부터 · 719-2 몫)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:32:32 +09:00
eomsangdonandClaude Opus 5.5 dd4bbb3c61 knowledge(요구): 요구 554 라이다 외부 도구 시험 결과 — 평활해도 0.77→0.75 m · 업체 분류+TIN 0.12 m · SMRF 0.27 · 권함 = 격자만 TIN
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:31:57 +09:00
eomsangdonandClaude Opus 5.5 702ab1daa8 feat(714-2): 공용 단가산출 계산서 묶음 꼴 — 인력 · 재료 · 경비(장비) · 하위(714-1 줄 덧칸 묶음) · 장비 · 하위 로직 = 한 줄 + 노 · 재 · 경 칸 · 누르면 몫 식 석 줄 펼침 · 묶음 머리 기준단위 · 환산 근거 · 쓰는 입력 칸 · 맨 아래 비목 소계 셋 + 계
- calc_sheet.ts bundle 꼴 · bundleLines(순수 · 같은 이름 k 번째끼리 짝) · 새 calc_sheet.css(장비 줄 누름 · 몫 들여씀)
- M01 「인력·재료·경비」 = bundle 꼴 · 변수 칸 = 서버 쓰는입력
- 714-3 텍스트 수식 단추도 서버 쓰는입력 먼저(조건 · 재료 고르기 입력까지) · 없으면 조각
- 704-2 M02 계산서 보기 = 비목 꼴 그대로(DOM 전후 같음)
- 장비 펼침 넉 줄(손료 · 주연료 · 잡품 · 운전사) = sub3 「펼침」 덧칸 뒤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:30:33 +09:00
eomsangdonandClaude Sonnet 5.5 cf6e2cf725 test(717-4): 이음매 시험 자료에 일위대가 단위 「㎡」 추가
715-3 일위대가 단위 빈칸 막기와 맞물려 단위 없는 시험 자료가 실패하던 것 수정

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 16:21:28 +09:00
eomsangdon 4df7f1eb4e Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_1' into sub_laptop_7 2026-11-01 16:20:02 +09:00
eomsangdonandClaude Opus 5.5 5f36e85d7d fix(합침): ui_template_locale_m2 충돌 표시 걷음 — 717-2a 단가산출 키 · 717-4 고르기 키 양쪽 살림(6561c90ab 합침에 표시가 남았던 것)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:17:38 +09:00
eomsangdon 6561c90ab5 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1
# Conflicts:
#	ui_template/ui_template_locale_m2.ts
2026-11-01 16:15:34 +09:00
eomsangdon 8c62765fd7 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-01 16:14:19 +09:00
eomsangdon 1765901ad2 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_1 2026-11-01 16:14:05 +09:00
eomsangdon 301a434402 Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_2 2026-11-01 16:13:05 +09:00
eomsangdon 68bc3bd0c8 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_2 2026-11-01 16:12:59 +09:00
eomsangdon 290f087c73 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_2 2026-11-01 16:12:56 +09:00
eomsangdon 472c78cd77 Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_5 2026-11-01 16:12:55 +09:00
eomsangdon 87545ea4c7 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_5 2026-11-01 16:12:48 +09:00
eomsangdon 431973435b Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_5 2026-11-01 16:12:45 +09:00
eomsangdon cbeec5a1cd Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_5 2026-11-01 16:12:41 +09:00
eomsangdon 8f7fefe438 Merge remote-tracking branch 'origin/dev' into sub_laptop_2 2026-11-01 16:12:26 +09:00
eomsangdon 5188ec92ec Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-11-01 16:12:15 +09:00
eomsangdonandClaude Opus 5.5 36a21591e2 fix(715 · 712-2 이음): M02_Sangeun_Calc.times_of — 옛 Store_Combo._PER 대신 단위 한 벌 master_unit.per(712-2 가 옮김) · 합친 뒤 시험 63 통과
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:11:09 +09:00
eomsangdonandClaude Opus 5.5 ccabbad6fb feat(719-2): 공사 쪽 M02 단가산출(SK) — B09 대가 블록 · 산근 · [업데이트] · 내 자료집 SK
- Labor_Source: 굳힌 SK = 품셈 꼴 대가 블록(사본 단가.json 굳힌 키 줄 · M01 안 읽음) · 단가산출 속 일위대가 · SK 줄 = 그 대가 블록 부르는 줄 · 고정 줄 = fixed · 단가 고친 줄 = fixed(M02 셈 단가) · cover = P:+SK 이름표(SK 고쳐도 B09 덮개 그대로) · 줄 sk · poom(품셈 값) · 부르는 줄 수량 ÷ 굳힌 줄 배 · 사전조합 알약 = 굳힌 알약 · 서고 sangeun 사본
- Sangeun: SK 하위 로직 = 산근 · 단가산출 속 참조 걸음 · 결과단위 배 = 사본 줄에서(_unit(row) · unit_of(whole, code))
- Linked: 굳힌 SK 자원 키(굳힌 키 로직 줄 · 고정 이음)
- Refresh: 미리 보기 단가산출(바뀌는 SK 줄) · 서고 사본 원본판 다르면 다시 굳힘
- Bill_Library: 종류 sangeun(내 자료집 SK 불러오기) · changed · refresh_copy(같은 폴더 · 값 그대로 · 원본판 없는 옛 사본 그대로)
- Daega_Personal: 품셈 블록(굳힌 SK 포함) → 개인 층 SK(B09 품 · 단가 덮개 = SK 덮개 · 더한줄 수량 · 단가)
- M02_Item_Sangeun: 굳힌 SK 정체(굳힌 줄 차례 → 원본 cid)
- 화면: 일위대가 펼침 SK 줄 되돌림 [단가산출 값으로] · 곁 글 품셈 값 · 내 자료집 단가산출 글 · 업데이트 미리 보기 SK 줄 · 단가연결 내 것 단가산출(Labor_Api fetchLibrary 종류 넓힘)
- 시험 test_719_2_sk_project 5 통과 + 1 건너뜀(715-1b 엔진 단가 · 값묶음 뒤 자동 실행)
- test_155_5: _unit 인자 = 로직 줄로 바뀜(사본 줄에서 배 읽음) — 부르는 줄 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:09:53 +09:00
eomsangdon 9ca48fa581 Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-01 16:06:33 +09:00
eomsangdonandClaude Opus 5.5 9d72d80b53 feat(717-4): 일위대가 [로직 추가] 「단가산출」 탭 · B09 재료 창고 단가산출 출처 · 단가산출 더한줄 고르기
- 새 M02_MasterTemplete_Sangeun_Pick — listSangeun = 위 층 SK 목록(마스터 › 회사 › 개인 · 금액 = 층 목록 기준금액) · openSangeunLinePicker = SK 더한줄 고르기(M01 로직 · 일위대가 · 단가산출 · M01 재료 → 715 더한줄 꼴)
- Labor_Pick — lib.sangeun 이면 탭 [기준 목록 | 단가산출] · 단가산출 탭 = 기준 목록 표 꼴(출처 = 단가산출 · 층) · 줄 누름 = 참조 {층, 주인, SK 키}
- withHopyo(일위대가 [로직 추가]) 에 sangeun 켬 · B09 단가연결 창은 그대로(SK 공사 연결 = 719-1 뒤)
- B09 재료 창고 출처 탭 「단가산출」 — SK 목록 · 연결은 알림만
- locale_m2 키 넷 · 시험 test_717_4_sangeun_pick

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:06:31 +09:00
eomsangdonandClaude Sonnet 5.5 ea1f749d99 test(717-2a): 낡은 시험 339 고침 — 불러오기 끝 새로고침이 Promise.all 두 목록(일위대가 · 상은)으로 바뀐 꼴에 맞춤
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 16:05:26 +09:00
eomsangdonandClaude Opus 5.5 dfa72ac292 test(715-1b): auto 커밋에 쓸려 올라간 엔진 덧칸 시험을 뺌 — 엔진 덧칸(sub3 712 뒤) 전엔 실패 · 715-1b 때 다시 넣음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:05:18 +09:00
eomsangdon 60f03604c3 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
# Conflicts:
#	M01_MasterData/M01_MasterData_Router.py
2026-11-01 16:05:01 +09:00
eomsangdon fdd781ca46 auto: 2026-11-01 16:04 (ESD_LAPTOP) 2026-11-01 16:04:58 +09:00
eomsangdonandClaude Opus 5.5 4585690465 style(715-3): 715 시험 머리 글 줄 길이 · fixture 받아 씀 noqa
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:04:57 +09:00
eomsangdonandClaude Opus 5.5 b62a1d9356 feat(715-3): 일위대가 줄 덮개(패치) · SK 참조 줄 · [단가산출로 저장] 길 · 일위대가 단위 빈칸 막기 · 「1 단위 당」 머리
- 새 M02_Labor_Patch.py — 줄 칸 패치 = SK 꼴 {덮개, 뺀줄, 더한줄}(한 벌) · 로직 줄 · SK 참조 줄만 · 줄 고름 · 자재표시(차례)를 cid 로 바꿔 뺀줄 뒤 제자리 · 셈 = 이름 없는 SK 로 M02_Sangeun_Calc
- M02_Labor_Doc — clean 이 패치 정리 · calc 로직 줄 · 참조 줄 패치 갈래(몇 줄 · 690 줄)
- M02_Labor_Expand — 사전조합 안 줄 패치 갈래 · 머리 「1 ㎡ 당」
- M02_Labor_Ref — 사전조합 줄에 패치 남김 · 줄 있는 일위대가 단위 빈칸 400(빈 새 문서는 받음 — 화면 새로 단추가 단위 null) · 409 글 종류 이름 사전조합 → 일위대가 · labor → 구조물 조합
- POST /api/m02/layers/{층}/sangeun/from-line(층 쓰기 권한) — 새 SK + 바꿀 참조 줄 · 셈 답에 머리
- Labor_Calc.ts 짝은 그대로(settle 은 서버 기준을 받음 · 거울 시험 통과)
- 낡은 시험 고침: test_m02_labor_ref · test_175_precombo_mats · test_204_2_b09_bill_library 의 줄 있는 사전조합에 단위(빈칸 막기) · 참조 줄 분모 = 그 사전조합 단위
- 새 시험 test_715_3_labor_patch.py(임시 storage)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:04:31 +09:00
eomsangdonandClaude Opus 5.5 9875743181 feat(714-1): 계산서 줄 덧칸 — 쓰는 입력 · 묶음 · 기준단위 · 환산 근거 · M01 /text 와 M02 계산서 같은 줄 꼴
- 새 master_sheet_line.annotate — master_text.lines 답에 줄마다 덧칸(줄 차례 = 호표 _shares 비목 · 뒤 덧줄) · 머리 기준단위(결과단위 분모)
- 쓰는입력 = 수량 식 · 요소 · 덧줄 식이 쓰는 로직 입력(중간값 거슬러 감) · 기준단위 = 줄이 찾는 표 기준(712-3 basis) · 환산근거 = 표 기준배 ↔ 결과단위 배 · 재료 품목 단위 ↔ 줄 단위(712-2 note)
- M01 /text = 새 Store_Sheet 가 감쌈(Store.py · master_text.py 700 줄 근처라 안 늘림) · M02 /logic-lib/sheet 줄 칸에 같은 KEYS
- 지킴 시험 test_714_1_sheet_line(두 길 같은 덧칸)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:04:02 +09:00
eomsangdonandClaude Opus 5.5 881c204ca8 feat(712-2): 단위 한 벌 master_unit — 별칭 · 개수 묶음 · 결과단위 배 읽기 · 같은 성격 환산 · 근거 글
- 별칭(master_material) · 같은 성격 환산(master_pick) · 결과단위 배 읽기 셋(Store_Combo · Hopyo_Ours · master_basis) · 보이는 꼴 별칭(master_basis) 갈아 끼움 · 엑셀 맞춤(Hopyo_Match)은 master_pick 이름 그대로 이음
- 개수 묶음(개 · EA · 본 · 매) 1:1 — 본 ↔ 개 단위경고 4 줄 사라짐 · 값 같음
- 부피 ↔ 무게 = 단위중량 표 CC000337 고른 줄 받을 때만(기본 줄 없음) · 밀도 · ㎾h 경고 글만
- B08 /1000 · /40 손 식 = 실무 엑셀 G열 식이라 그대로(시험이 같은 값 대조)
- 지킴 시험 test_712_2_unit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:03:54 +09:00
eomsangdonandClaude Opus 5.5 cddb28b63a feat(714-2): 공용 단가산출 계산서 틀 — A00_Common/calc_sheet.ts(704-2 계산서 줄 올림 · 묶음 머리 변수 칸 · 바꾸면 onInput) · M02 계산서 보기 · M01 「인력·재료·경비」 보기가 한 벌로 씀(장비 한 줄 · 기준단위 · 환산 근거는 714-1 뒤)
- 704-2 Labor_Sheet 줄 · 소계 · 계 = calcSheetRows(표본 셋 DOM 전후 같음)
- 새 M01_MasterData_UI_LogicLab_Est.ts — /text 묶음 그대로 · 묶음이 쓰는 시험 입력(714-3 usedInputs) = 머리 변수 칸 · 「준비 중」 자리 걷음
- 낡은 시험 test_704_logic_standard 고침 — 계산서가 계산 줄 부품을 공용 계산서 안에서 씀(import 자리만 바뀜)
- 새 지킴 시험 test_714_2_calc_sheet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:03:36 +09:00
eomsangdon 8e2aac9717 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_2' into sub_laptop_6 2026-11-01 16:02:56 +09:00
eomsangdonandClaude Opus 5.5 6d5b78f83e feat(712-1): 인력 · 재료 · 경비 묶음 셈 때 파생 · 기계경비 판별 한 곳
- 새 master_bundle — 호표 · 덧줄 = 비목 따라 · 비목 없는 로직 줄 = 기계경비면 경비 + 장비 · 아니면 하위
- 기계경비 판별 = 결과단위 원/시간(is_machine) — M02 엑셀 맞춤(이름 「시간당」) · B09 산근 · 산근 텍스트(입력 「기계」) 갈아 끼움
- 엔진 run 줄 덧칸 「묶음」 · 기계경비 줄 「장비」(출력 꼬리표만 · 노 · 재 · 경 몫은 줄 비목 그대로)
- 정본 칸 안 더함 · 5,911 줄 중 하위 31 · 기계경비 줄 1,044
- 지킴 시험 test_712_1_bundle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:02:13 +09:00
eomsangdon 835a98bad9 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-01 16:01:07 +09:00
eomsangdonandClaude Opus 5.5 a64e3ca398 feat(717-2a): M02 왼쪽 맨 위 「단가산출」 칸 — 층 단추 따로(기본 개인) · 폴더 나무 · 목록표 · 상세 자리 · 대시보드 줄
- 새 UI_Side_Sangeun.ts — 서고 칸(716-4) kind sangeun · 목록표 고름 sangeunList
- 새 Sangeun_View.ts — 이름 · 단위 · 바탕 · 덮개 · 더한 줄 · 뺀 줄 · 열린 입력 요약 + 상세 자리표(717-2b 가 갈음)
- UI_Main 종류 sangeun 갈래 · 목록표 조건에 sangeun · Library_List 열 표(단가산출 = 바탕 · 고친 줄 · 쓰는 곳) · sangeunBlank(단위 식 · 바탕 없음)
- UI_Page 단가산출 층 상태 sk · UI_Side 맨 위 칸 · reveal 「단가산출」 · Layer open 「단가산출」 · Api Kind sangeun
- m_settings_nav 대시보드 마스터 템플릿 묶음 「단가산출」 줄(?open=단가산출) · locale_m2 키
- 낡은 글 대조 시험 넷 고침(197_1 · b01_settings_cards · m02_layer_screen · m02_precombo_scope) — 대시보드 줄 하나 늘고 OPENS · reveal · 목록 열 표 · 칸 차례가 바뀜

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:00:03 +09:00
eomsangdonandClaude Opus 5.5 c0851ee63b feat(717-2a): 단가산출 목록 줄 덧칸 — 바탕(로직 키 · 이름 | 참조 SK | 없음) · 고친 줄 수 · 쓰는 곳 수
- M02_Labor_Ref.summary sangeun 갈래만(브레인 답 A · 그 함수 밖 안 만짐)
- 지킴 시험 test_717_2a_sangeun_list 2(임시 storage)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:59:51 +09:00
eomsangdonandClaude Opus 5.5 915feb436e feat(714-3): M01 텍스트 수식 입력 단추 — 줄이 쓰는 시험 입력(표 찾기 행 열쇠 · 이름 조각)을 값 식 아래 단추로 · 누르면 그 자리 고르기/글 칸 · 바꾸면 시험 계산 입력 고침 + 다시 셈 · 정본 · 캐시 안 바뀜
- Formula.ts usedInputs(순수) · inputChip(부품 상자 단추 · 고르기 · 입력 칸)
- Detail → Formula 에 입력 · 값 · onInput 넘김 · LogicLab syncLeft(loud) = 흐름 예시값도 같은 길 · 줄 수 그대로(627)
- Logic_Api TextPiece 에 이름 조각 값 칸
- 새 지킴 시험 test_714_3_formula_inputs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:49:52 +09:00
eomsangdon cf7461749d Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-01 15:44:19 +09:00
eomsangdon 288af5cf9e Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_6 2026-11-01 15:43:13 +09:00
eomsangdon b9373d6c7f Merge remote-tracking branch 'origin/dev' into sub_laptop_7 2026-11-01 15:42:21 +09:00
eomsangdonandClaude Opus 5.5 cfc5309a4d fix(716-3): Hopyo 화면 글 「사전조합」 → 「일위대가」 — M02_PrecomboTitle 키로
- 확인 창 [일위대가 생성] 단추 · 엑셀 보기 · 다시 잇기 · 장부 · 고르기 알림 · 구조물 조합으로 설명
- 서버 줄 상태 글 「확인된 일위대가」
- test_m02_hopyo_screen 단추 글 단언을 키 꼴로 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:42:14 +09:00
eomsangdon f8c628552c Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_2 2026-11-01 15:42:12 +09:00
eomsangdon b4eb4b7521 Merge remote-tracking branch 'origin/dev' into sub_laptop_2 2026-11-01 15:42:09 +09:00
eomsangdonandClaude Opus 5.5 3d4bcaea59 feat(713-2): M01 로직 정본 개정 확인 창 — /save 개정 409 받으면 바뀌는 정본 줄(파일 · 로직 · 바뀜) 창 · 「원문 개정 반영」 확인 뒤 같은 몸에 개정확인 true 다시 보냄 · 취소 = 캐시 그대로
- 새 M01_MasterData_UI_LogicLab_Revise.ts(reviseHits · askRevise · saveRevised) · 부품 상자 모달 · 목록 줄 · 배지
- LogicLab [저장](흐름 저장 · 지우기 · 더하기 같은 길)이 saveRevised 로 · 줄 수 그대로(627)
- Logic_Api saveFiles 에 개정확인 인자(기본 false)
- 옛 409 stale 은 토스트 그대로
- 새 지킴 시험 test_713_2_revise_ui

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:42:00 +09:00
eomsangdon ec49810665 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-01 15:41:50 +09:00
eomsangdon f59bae54b1 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-01 15:40:19 +09:00
eomsangdon 94d8f1261d Merge remote-tracking branch 'origin/dev' into sub_laptop_1 2026-11-01 15:39:58 +09:00
eomsangdonandClaude Opus 5.5 69d989e5c8 feat(717-1): 단가산출 상세 부품 길 주입 · M02 logic-lib 읽기 · 셈 길 넓힘
- Logic_Api.ts · Api_Fetch.ts · Logic_Note.ts — `/m01` 박힘 대신 setLogicApiPaths({ read, calc }) 길 묶음 · 안 넘기면 /m01 그대로 · 저장 · 만들기 · 관리자 길은 늘 /m01
- 새 M02_Logic_Lib_Read.py — /api/m02/logic-lib/ 로그인 길(logic/auto · logic/flow · elements · pick · materials · rows(인력 · 재료 · 기계만) · table(소요량 · 계수만) · calc · text) · M01 길 함수 그대로 부름 · row = 저장 안 한 줄 셈만 · 쓰기 없음
- main.py 등록(로그인만) · 지킴 시험 test_717_1_logic_lib.py + helper

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:39:36 +09:00
eomsangdon 1124cf26f6 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_8 2026-11-01 15:36:19 +09:00
eomsangdon a005244a17 Merge remote-tracking branch 'origin/dev' into sub_laptop_6 2026-11-01 15:35:33 +09:00
eomsangdon 878667f7bd Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-11-01 15:35:31 +09:00
eomsangdonandClaude Opus 5.5 ceec2a99da feat(716-4): 서고 나무 폴더 — 종류 인자 · 안에 든 것째 지움 · 쓰는 곳 409 · 여럿 · 복사 붙여넣기
- 서버 `M02_Precombo_Folders` — 종류 인자(precombo · sangeun · 자리 파일 `{종류}_폴더.json`) · 폴더 지우기 = 안의 문서 파일째(모두 검사 뒤 한 번에 · 바깥에서 쓰면 409 + 그 이름 · 지우는 것끼리 참조는 됨) · 문서 지우기 · `대상` 여럿 옮기기 · 지우기 · 붙여넣기(새 키 · 본뜬 = 원본 · 같은 폴더 이름 「(1)」)
- 길 `M02_Labor_Router` — `/layers/{층}/{종류}-tree` GET · POST · `/paste`(옛 precombo-tree 주소 그대로)
- 화면 새 `UI_Side_Library.ts` — 일위대가 · 단가산출 두 칸이 같이 쓰는 서고 칸(Ctrl · Shift 고름 · 오른쪽 클릭 · Ctrl+C · Ctrl+V · Delete · 지우기 확인 창 · 끌어 옮기기 여럿) · `UI_Side_Precombo.ts` = 일위대가 몫만 넘김
- `Api_Fetch` — 나무 부르기 종류 인자 · `pasteLibraryTree` · `newLibrary` 종류 sangeun
- 시험 — 새 `test_716_4_library_folders.py`(임시 storage) · 낡은 것 고침: `test_m02_precombo_folders`(빈 폴더만 지움 → 안에 든 것째 · 「없는 사전조합」 → 「없는 문서」 · FILE → file_of) · `test_m02_precombo_scope` + helper(나무 짜기 · 서고 길 글이 Library.ts 로 옮김)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:35:23 +09:00
eomsangdon 75b65e03a2 Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_2 2026-11-01 15:35:19 +09:00
eomsangdonandClaude Opus 5.5 b54950d28b feat(719-1): 프로젝트 사본 SK 풀어 굳힘 — 고친 줄 = 사본 단가.json 로직 줄 · 굳힌 키 GX9##### (SK 이름표 해시 · 같은 SK = 같은 키) · B09 서고 manifest 원본판
- 새 M02_Item_Sangeun: 참조사본 SK → M02_Sangeun_Calc.resolve 고친 줄 → 로직_단가산출.json 줄 · 사본 SK = 바탕 굳힌 키 · 덮개 0 · 원본 · 바탕판 · 짝잃음 근거
- M02_Item_Source: labor_doc_rows 가 굳힘 · price_bundle 이 굳힌 줄과 그 줄이 닿는 마스터 줄 · 고른 자재 담음
- M02_Item_Prices: 수집기 줄 하나 길(row) 떼어 둠 · 요소 없는 줄(참조 · 고정 더한줄) 건너뜀
- B09 Bill_Library: 서고 사본 manifest 원본판 = 일위대가.json 지문(source_version)
- 시험 test_719_1_sk_freeze 4(임시 storage)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:34:29 +09:00
eomsangdon 28c773e1cd Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-11-01 15:33:59 +09:00
eomsangdon bebd0af063 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-01 15:33:58 +09:00
eomsangdon 7128579f92 Merge remote-tracking branch 'origin/dev' into sub_laptop_7
# Conflicts:
#	resources/tester/test_m02_hopyo_screen.py
2026-11-01 15:33:12 +09:00
eomsangdon 43eb6b7ad6 Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_7 2026-11-01 15:32:06 +09:00
eomsangdon 9444c936f1 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_7 2026-11-01 15:32:02 +09:00
eomsangdon 77a0ae5a10 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_7 2026-11-01 15:32:00 +09:00
eomsangdon ab32f21521 Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_7 2026-11-01 15:31:58 +09:00
eomsangdon 89d8caf942 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_7 2026-11-01 15:31:56 +09:00
eomsangdon e4f65e7021 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_1 2026-11-01 15:30:42 +09:00
eomsangdon 808a07eab2 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_1 2026-11-01 15:30:40 +09:00
eomsangdonandClaude Opus 5.5 0c956a6c61 feat(711-2): M01 재료 모달 하나 — 상세 표 줄 · 시험 계산 자재 칸 같은 모달 · 물가 넷 가로 · 찾기 · 고르기
- 상세 쪽 openMaterialModal 기준 — 위 물가자료 · 유통물가 · 물가정보 · 거래가격 가로 넷(빈 칸 「-」 · 시험 계산이 쓴 값 굵게)
- 후보 = /materials(조건 · 단위 거름) · 조건 없으면 이름 찾기 · 찾기 칸(검색어로 먼저 거름 · 없으면 재료 전체 찾기) · 목록 값 열 뺌
- 줄 누르면 고름(시험 입력 · 저장 안 함) → 다시 셈 · 상세 표 재료 줄에서 골라도 시험 계산 자재 칸 따라감
- 기계 고르기 · M02 일위대가 [바꾸기]는 PickTable 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:26:22 +09:00
eomsangdonandClaude Opus 5.5 cc588889f6 feat(718-1): 불러오기 결과 「단가산출」 켬 — 산근 블록마다 바탕 없는 단가산출(715-2 받음)
- 새 M02_Hopyo_Sangeun.py — 단산 블록을 부르는 첫 엑셀 줄 1 단위 단가를 고정 더한줄 하나로 · 본뜬 「HP-###### 단산N」 · 같은 본뜬은 그대로(겹침 없음)
- 길 POST /layers/{층}/hopyo/{키}/sangeun · [끝내기] 지문 기록에 단가산출도 셈
- 마법사: 결과 「단가산출」 체크 켬 → ② 걸음 [단가산출 만들기] · 알림 글 「일위대가 · 단가산출」
- 시험: test_718_1 단가산출 더함 · 낡은 대조 고침 — 716-3 왼쪽 판 차례(extras 없어짐) · SANGEUN_READY 대조
- 고정 더한줄 셈 값(엔진 값묶음)은 715-1b 뒤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:22:27 +09:00
eomsangdonandClaude Opus 5.5 d27dba4f23 test(716-3): 낡은 시험 둘을 716-3 꼴로 고침 — M02 왼쪽 칸 차례 · reveal 옵션 갈래 없음
- test_m02_hopyo_screen::test_좌측_판_자리와_권한 — 옛 `extras ?` 자리 대신 일위대가 › 구조물 조합 › 구분선 › 불러오기 차례 확인
- test_197_1_dashboard_option_card::test_reveal_옵션만_줄을_누름 → test_reveal_옵션은_M01_로 — 옵션 정의가 M01 로 옮겨 M02 reveal 은 일위대가 · 도면만 펼침
- 기능 흠 아님 · 716-3 이 UI_Side.ts 를 바꿔 옛 꼴 기대가 낡음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:21:56 +09:00
eomsangdon da14e24154 Merge remote-tracking branch 'origin/dev' into sub_laptop_4 2026-11-01 15:21:30 +09:00
eomsangdonandClaude Opus 5.5 54fc156f18 feat(713-3): 로직 목록 덧칸 서버 — /logics 줄마다 묶음(같은 절) · 작업량 부모 · 부르는/불리는 수 · 관련(비고 절 번호) · 정본 그대로 · 새 파일 Store_List · 인재경 줄 수는 712-1 뒤
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:21:17 +09:00
eomsangdon 28704549d4 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-01 15:21:05 +09:00
eomsangdon 4f8b92b270 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-01 15:21:04 +09:00
eomsangdon ee908fae28 Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_6 2026-11-01 15:19:50 +09:00
eomsangdon 1a931e2ea1 feat(709-2): M01 표 · M02 목록 줄 수 = 화면 높이 · 쪽 단추 표 아래 · 한 장 표 화면 안 스크롤
- 쪽 있는 표(요소 · 표 목록 · 로직 목록 · 재료 고르기 모달 · M02 서고 · 새 항목 목록): 줄 수를 표 칸 위치 ~ 화면 아래로 셈(watchFit) · 창 크기 · 글꼴 늦게 실림에도 다시 셈 · 쪽 크기 바뀌어도 첫 줄 유지
- 쪽 단추 = 표 바로 아래 · 줄이 모자라면 표와 함께 위로 붙음
- 쪽 없는 한 장 표(소요량 · 계수): 높이를 화면 아래로 묶고 안에서 스크롤(watchHeight · 표 아래 주 높이 뺌)
- UI_Style.css 의 max-height calc(100dvh - 300px) 제거 · m01-master__pager 규칙 제거(부품 ui-pager 로)
- 낡은 점검 51-10(쪽 단추가 거름 줄 안에 있다고 봄) = 표 바로 아래 ui-pager 로 고침
2026-11-01 15:19:35 +09:00
eomsangdon 3a125d6088 Merge remote-tracking branches 'origin/main_laptop_1', 'origin/sub_laptop_2' and 'origin/sub_laptop_4' into sub_desktop_1 2026-11-01 15:19:11 +09:00
eomsangdonandClaude Opus 5.5 c888a995b7 knowledge(요구): 요구 554 원문 기록 — 라이다 지금 로직 그대로 · 데스크탑 서브 git 밖 폴더에서 외부 도구 시험 · 결과 아티팩트
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:17:34 +09:00
eomsangdonandClaude Opus 5.5 4953848768 feat(711-1): M01 시험 계산 — 옵션 · 자재 격자 둘 + 수평 구분선 · 기본 4 열 · 자재 이름 누르면 고르기 모달
- 옵션 고르기 · 자재 고르기 사이 709-1 수평 구분선 부품(createDivider)
- 기본 4 열 · 긴 이름 칸 두 칸
- [품목 고르기] 단추 걷음 — 잡힌 품목 이름 링크를 누르면 모달 · 고르면 다시 셈

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:16:23 +09:00
eomsangdonandClaude Opus 5.5 7cf504a7c7 feat(713-1): 로직 정본 개정 확인 서버 — /save · 흐름 저장 · 지우기 · 더하기가 로직 정본 줄을 바꾸면 개정확인 없이는 409(개정 · 까닭) · 같은 줄 다시 보냄 · 요소 · 자체는 대상 밖 · 줄 바탕 지문 row_print(계산 칸) · GET /logic 지문 · 새 파일 Store_Revise
낡은 시험 고침: test_m01_api · test_m01_cache · test_m01_logic_flow_save · test_208_1_logic_alias · test_m01_logic_flow_project 가 임시 사본의 정본 로직 줄을 고쳐 저장 — 이제 개정 확인이 필요하므로 몸에 개정확인 true(뜻 그대로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:15:30 +09:00
eomsangdonandClaude Opus 5.5 a0bd269d7f feat(710-2): M01 단가산출 상세 보기 고르기 — 머리줄 분할 고르기 · 고른 덩이만 · 기본정보 접힘 한 줄 · 지금 입력
- 새 M01_MasterData_UI_LogicLab_View.ts — 보기 넷(단가산출 상세 기본 · 인력·재료·경비 준비 중 · 시험 계산 · 텍스트 수식) · [신규 로직] 왼쪽 세로 나눔선
- 덩이 이름표 data-block + 그릇 data-view CSS 숨김 — 시험 계산은 숨어도 그려 두어 자동 계산 돎 · 로직 바꿔도 보기 유지
- 흐름 로직(FlowView) 두 덩이도 같은 이름표
- 기본정보 = 맨 위 접힌 한 줄(키 · 원문번호 · 이름 · 결과단위) · 막힘 까닭은 접어도 보임
- 단가산출 상세 머리 「지금 입력」 한 줄(읽기만)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:13:23 +09:00
eomsangdon 5068b6d3f7 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-11-01 15:10:05 +09:00
eomsangdon c55c2fb828 Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-11-01 15:10:04 +09:00
eomsangdon f7b0a96923 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_7
# Conflicts:
#	ui_template/ui_template_locale_m2.ts
2026-11-01 15:09:17 +09:00
eomsangdonandClaude Opus 5.5 c6df183983 feat(718-3): M01 별칭 장부 화면 — 왼쪽 칸 · 모인 후보 표(이름 · 짝 · 회사 수 · 사람 수 · 충돌) · 승인 · 반려 · 마스터 장부 보기 · 지움 · 대시보드 줄
- 새 M01_MasterData_UI_Alias — 모인 후보 / 마스터 장부 탭 · 승인 · 반려 · 지움 = 확인 창 뒤 서버(718-2 길) · 마스터 장부 = M02 별칭 장부 부품 그대로(마스터 층) + 마스터 공사 내역 맞춤
- M01 왼쪽 판 = 구조물 옵션 정의 아래 「별칭 장부」 칸(본 칸 = 옵션 정의와 같은 자리) · 주소 ?open=별칭(M01 페이지 한 줄)
- 대시보드 설계 기본값 마스터 데이터 묶음에 「별칭 장부」 줄(m_settings_nav 한 줄 · 화면 글 키 하나)
- 시험 test_b01_settings_cards · test_197_1 = 줄 하나 늘어난 자리 · 별칭 칸 대조로 고침(낡은 줄 번호)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:09:14 +09:00
eomsangdon a5e235e08f Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_7 2026-11-01 15:08:56 +09:00
eomsangdon bee3c898ac Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_7 2026-11-01 15:08:52 +09:00
eomsangdon 295a6a73e5 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_7 2026-11-01 15:08:50 +09:00
eomsangdon 9304130c62 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_7 2026-11-01 15:08:49 +09:00
eomsangdon 1bbffcd2ae Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_7 2026-11-01 15:08:47 +09:00
eomsangdonandClaude Opus 5.5 f49b55a3d8 feat(718-1): 일위대가 불러오기 = 한시 마법사 — 결과 층 고름 · [끝내기] 때 올린 파일 지움 · 같은 엑셀 겹침 막음
- 좌측 「일위대가 실무자료」 → 「일위대가 불러오기」(locale M02_HopyoImport) · 옛 파일 목록 걷고 [불러오기] → 본 칸 마법사(새 M02_MasterTemplete_Hopyo_Wizard.ts)
- 마법사 = ① 결과 층(쓸 수 있는 층만 · 기본 개인) · 결과 체크(일위대가 · 단가산출은 715-2 뒤) · 엑셀 고르기 · 작업 중 이어서 → ② 엑셀 보기 창(모두 고정으로 들여오기 · 구조물 조합으로 그대로) → ③ 끝내기
- 서버: 올림 지문(sha256) · 작업 중 같은 엑셀 = 이어서 · 끝낸 같은 엑셀 409 · 지우기 = 끝내기(HP · 격자 · 맞춤셈 캐시 지움 · 만든 일위대가 있으면 지문 기록 · 별칭 장부 남음) · HP 번 다시 안 씀 · 목록 = 지문 있는 것만(옛 HP · 붙여넣기 HP 파일 그대로 숨김)
- 시험: 새 지킴 test_718_1_hopyo_import(임시 storage) · 낡은 시험 고침 — 같은 엑셀 다시 올림이 새 키를 내던 가정(excel · grid · flow) · 붙여넣기 HP 목록 가정(705_2) · 좌측 판 글 대조(screen)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:08:17 +09:00
eomsangdon cacc16ba03 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-01 15:05:42 +09:00
eomsangdonandClaude Sonnet 5.5 ccc2fe080f feat(716-3): M02 왼쪽 칸 차례 · 이름 · 구분선 — 일위대가 › 구조물 조합 › 구분선 › 불러오기 · 옵션 줄 뺌
- 일위대가(옛 사전조합) 제목 locale 키 M02_PrecomboTitle · 나무 안 글 · 마우스 오른쪽 메뉴 · 빈 글을 일위대가로
- 구조물 옵션 정의 읽기 줄 뺌(M01 에 입구 있음) · open=옵션 은 M01 로 그대로 돌려보냄
- 시험: PRECOMBO_TITLE 상수 기대 → locale 키 기대로 고침 · 칸 차례 시험 더함

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 15:04:41 +09:00
eomsangdon 287f0824fc Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-01 15:02:35 +09:00
eomsangdonandClaude Opus 5.5 9387f2642a knowledge(요구): 내역 15 원문 기록 · 14-5 원문 대조 — 조종원 환산 빼고 본문대로 · 쪼갠 12 합침 · 터파기 한 꼴
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:02:07 +09:00
eomsangdonandClaude Opus 5.5 368216594f fix(715-2): 단가산출 셈 길도 저장 전 문서 참조를 위 층만 · 있는 문서 · 순환 검사(남의 개인 층 읽기 막음)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:00:44 +09:00
eomsangdonandClaude Opus 5.5 9bcd8483af feat(710-1): M01 단가산출 상세 열 — 이름 · 수량 · 단위 · 로직 · 단가 · 금액 · 단가 1 행
- 수량 = 계산 답 고른 수량(% 덧줄은 율) · 계산 전 「—」
- 로직 열 = 옛 수량 칸 식 알약(단가 왼쪽)
- 단가 2 행 = 표 칸 돈 꼴 block 흠 — td 는 칸 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:58:52 +09:00
eomsangdonandClaude Opus 5.5 0b50669d4e feat(715-2): M02 단가산출 서고 종류 sangeun(SK-) · 참조 · 순환 422 · 지우기 막음 · 층 복사 + SK 셈 길(715-1b 몫)
- M02_Labor_Ref — PREFIX · TITLE · 키 SK · kind_of 셋째 갈래 · refs_in(담은로직 · 바탕 · 더한줄) 로 users · _no_loop · carry · frozen 이 SK 참조도 훑음 · clean_doc SK 갈래
- Expand 를 새 M02_Labor_Expand.py 로 뗌(refs.Expand 이름 그대로) · SK 갈래 = 1 단위 값
- 새 M02_Sangeun_Calc.py — 바탕 사슬 → 고친 줄 → M01 색인 덧씌움 + run_line(엔진 한 벌) · 저장 때 원값 · 바탕판 서버가 셈
- 새 M02_Sangeun_Router.py POST /api/m02/layers/{층}/sangeun/calc(저장 안 함 · 층 읽기 권한) · main.py 등록 한 줄
- Template_Layers LIBRARY_KINDS += sangeun · 뼈대 = 더한줄 · Labor_Router 서고 새로 길 Literal 한 줄
- 낡은 시험 test_m02_labor_ref 「LIBRARY_KINDS == (precombo,)」 → 새 종류 반영(점검 29)
- 새 시험 test_715_sangeun_store.py(임시 storage)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:58:36 +09:00
eomsangdon 27dc456d73 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_8 2026-11-01 14:55:12 +09:00
eomsangdon f32a9b3ecf Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-11-01 14:53:04 +09:00
eomsangdon 5e709d0c6b Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-01 14:53:03 +09:00
eomsangdon c27aaaf8ab Merge remote-tracking branches 'origin/main_laptop_1', 'origin/sub_laptop_5', 'origin/sub_laptop_6' and 'origin/sub_laptop_8' into sub_laptop_3 2026-11-01 14:51:53 +09:00
eomsangdon b903a0db49 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-01 14:50:48 +09:00
eomsangdonandClaude Opus 5.5 a36b1051d4 feat(712-3): 표 기준 → 기준배 · 기준단위 읽을 때 파생 — 표 기준 글 · 빈 기준은 원문 절 머리 · 정본 표 안 씀 · API 표 밖 칸 기준파생 · 판정규(판 = 읽을 때 파생) · check_master 「배」 대조 7 건 · 지킴 시험
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:50:06 +09:00
eomsangdonandClaude Opus 5.5 28e9a2a667 feat(718-2): 별칭 장부 시스템 모으기 서버 — 회사 · 개인 층 맞춤 + 공사 내역맞춤 후보 · 익명 · 충돌 · 승인 → 마스터 · 반려 기억
- 새 M01_MasterData_Alias(/api/m01/alias-ledger · 시스템 관리자만 · main.py 한 줄) — 공유 storage 읽기만 · 같은 짝 묶음(서명) · 회사 수 · 사람 수만 · 같은 열쇠 다른 짝 · 마스터와 다름 = 충돌 · 승인 = 서버가 다시 모아 찾음 → 맞춤 ledger.put 재검사 · 내역맞춤 마스터 기억 · 반려 = 마스터 층 별칭반려 기억 · 자동 반영 없음
- B09 잇기 기억 읽기에 마스터 층을 맨 아래로(브레인 허락) · test_707_4 기억 층 시험에 마스터 줄 한 줄
- 새 시험 test_718_2_alias_gather(임시 storage — 모음 수 · 익명 · 충돌 · 승인 → 마스터 줄 · 다른 회사 새 사용자가 씀 · 반려 기억 · 권한)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:50:02 +09:00
eomsangdonandClaude Opus 5.5 dd5e00f0ba knowledge(요구): 내역 14-6 EST 장비 줄 조사 — 장비 한 줄 + 노 · 재 · 경 칸 · 요구서 정함 6 반영
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:48:02 +09:00
eomsangdonandClaude Sonnet 5.5 a2742d528b feat(716-1·716-2): 옵션 칸 위 구분선 부품 · 시험 정리
- M01 로직 칸 아래 구분선 = 부품 createDivider
- 시험 셋 새 꼴로: 대시보드 줄 칸별(M01 3 · M02 3 · 계산 자리) · ☰ 페이지 둘(역할별) · 옛 open=옵션 돌려보냄 · M01 open 인자
- 낡은 시험 고침 까닭: 옵션 줄이 M01 로 옮겨 카드 순서 · 해시가 바뀜(test_b01_settings_cards · test_197_1 · test_309_2)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 14:46:48 +09:00
eomsangdonandClaude Opus 5.5 2939bff074 style(715-1a): M02_Sangeun_Doc 머리 글 줄 길이
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:44:30 +09:00
eomsangdonandClaude Opus 5.5 a9242671da feat(715-1a): M02 단가산출(SK) 문서 꼴 · 검사 · 줄 정체 kind:키#n · 덮개 · 더한줄 · 뺀줄 · 고친 줄 순수 함수
- 새 M02_Sangeun_Doc.py — clean(꼴 400 · 열린입력 = 배율만 · 원값 버림) · line_ids · patched(고름 · 자재표시 cid 열쇠 → 새 차례 · 짝잃음 남김)
- 엔진 안 만짐 · 시험 test_715_sangeun_doc.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:44:14 +09:00
eomsangdon d9d8cbc8e6 Merge remote-tracking branch 'origin/dev' into sub_laptop_6 2026-11-01 14:43:32 +09:00
eomsangdonandClaude Sonnet 5.5 a961e9cb69 feat(716-1·716-2): 구조물 옵션 정의 입구 M01 · 메뉴 표 둘 · M01 open 인자 (중간 — 구분선 부품 · 시험 정리 전)
- M01 왼쪽 단가산출 로직 칸 아래 구조물 옵션 정의 칸 · 본 칸 셋째 모드(저장 안 한 고침 물음)
- M02 옛 open=옵션 주소 = 시스템 관리자면 M01 로 돌려보냄
- settingLinks = 대시보드 칸별 줄(group · ?open=) · pageLinks = ☰ 페이지 둘
- 새 글 키 덧만: B01_Dashboard_CardLogic · CardStructOptionDef · CardPrecombo

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 14:43:23 +09:00
eomsangdon 2e43011069 feat(709-1): 쪽 단추 부품 · 수평 구분선 부품 · 부품 모음 견본
- ui_template_pager.ts: M01 buildPager 이동 + 화면 높이 줄 수 셈(fitRows) · 첫 줄 유지(pageKeepFirst) · 화면 크기 알림(watchFit)
- ui_template_divider.ts + overlay.css .ui-divider: 수평 구분선
- M01 buildPager = 새 부품을 부르는 옛 이름(PickTable · M02 목록 그대로)
- 부품 모음 견본 두 줄 · 지킴 시험 test_709_pager_fit.py
2026-11-01 14:40:53 +09:00
eomsangdonandClaude Opus 5.5 9827926a20 knowledge(요구): 요구 553 조사 둘 — 라이다 지금 코드 품질 · 무료 라이다 도구 비교
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:34:11 +09:00
eomsangdonandClaude Opus 5.5 c9d449f9e9 knowledge(요구): 요구서 내역12 사용자 확정 반영 — 이름 일위대가 · 고침 범위 · 모달 고침 자리 · 별칭 승인 · B09 근거 보기 · 수식 변경 = 시험 값
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:32:41 +09:00
eomsangdonandClaude Opus 5.5 f43b61b246 knowledge(요구): 내역 14 원문 기록 — 내역12 물음 여덟 답 · 갈린 로직 원문 대조 · EST 장비 줄 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:31:16 +09:00
eomsangdonandClaude Opus 5.5 c0b32805ed knowledge(요구): 요구서 내역12 점검 반영 — 정함 26 · 사용자 물음 8 · 빠진 걸음 셋 · 창 차례 · 점검 둘(구현 · 방향)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:22:58 +09:00
eomsangdonandClaude Opus 5.5 41be251ac5 knowledge(요구): 요구 553 원문 기록 — 라이다 입력 로직 무료 도구(QGIS 등) 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:05:53 +09:00
eomsangdonandClaude Opus 5.5 860dbe6408 knowledge(요구): 요구서 내역12 · 조사 넷 — M01 단가산출 로직 보기 · 엔진 바닥 · M02 단가산출 신설 · 메뉴 · 불러오기 · 프로젝트 복사(PLAN 709~719)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:01:06 +09:00
eomsangdonandClaude Opus 5.5 08def1d3db knowledge(요구): 내역 13 원문 기록 — 요구서 · 계획서 작성 뒤 구현 · 방향 점검
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:57:58 +09:00
eomsangdonandClaude Opus 5.5 23772023df knowledge(요구): 내역 12 원문 기록 — M01 단가산출 로직 보기 개선 · M02 단가산출 신설 · 폴더 · 연결 정보
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:30:09 +09:00
eomsangdonandClaude Opus 5.5 6dc0d27d4e knowledge(요구): 요구 552 원문 기록 — sub1 연결 전 백업 지움 허락
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:26:05 +09:00
eomsangdonandClaude Opus 5.5 294c126a39 knowledge(계획): PLAN 634~639 이관 — 요구 546~551 끝(아이콘 그룹화 · 노선 그리기 한 벌 · 가이드 추천선 · B05 라이다 · 노선 세트 · 작은 고침) · 메인 시험 374 통과 · PLAN 에는 621 만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:24:51 +09:00
eomsangdon 2fd90b7754 Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-01 13:22:28 +09:00
eomsangdonandClaude Opus 5.5 cb5c26d424 fix(637-5): 설계 지형이 라이다인 B 에 노선 못 덮는 LAS 올림 = 거절 · 설계 필터 파일 · 행 그대로(요구 546)
- B05 LAS 올리기: 설계 라이다 B 는 시험 자리(lidar_try)에 따로 지어 노선 덮임 검사 → 다 덮을 때만 본 자리로 옮김
- 못 덮음 = 「노선을 ○ % 만 덮음 — 설계 지형이 라이다라 바꾸지 않음」 · 옛 LAS · 모델 · 메타 · DB 행 그대로 · 시험 자리 지움
- 공용 promote_build — 입력 LAS 갈음 · processed · models 덮어 옮김(수치지형도 · 지도 그대로 · 수정시각 살아 행 지문 그대로)
- 수치지형도 설계 B(덧붙임) · 예정노선 길 그대로
- 시험: 반쪽 LAS 거절 · 파일 지문 · 메타 그대로 / 다 덮는 LAS 갈음 · 행 지문 = 옮긴 파일

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:21:51 +09:00
eomsangdon 6567d126f1 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1
# Conflicts:
#	B05_Profile/B05_Profile_UI_ExpectedDraw.ts
2026-11-01 12:52:48 +09:00
eomsangdonandClaude Opus 5.5 e6a420788f feat(635-5): B05 경위도 ↔ 사업지 좌표 서버 변환 길 · 찾기 이동 정확 · 속성 판 경위도(요구 546)
- POST /api/projects/{id}/route/coords — 작업 좌표계(resolve_project_crs · WKT 그대로) pyproj 묶음 변환
- 찾기 이동 = 서버 변환 좌표 · 바탕 tm_grid 늘려 쓰기(ponytail) 걷음
- 키트 lonLat = 노드 고를 때 한 번 묻기 · 같은 점 캐시
- test_635_3 찾기 줄 = 서버 변환 부름으로 고침(옛 tm_grid 부름 걷힘)
- 실측 검증_sub6 B: 50.4 km · 181.2 km 찾기 이동 어긋남 0.000 m · 경위도 = pyproj

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:51:03 +09:00
eomsangdon 5a7128ac3d Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-01 12:49:44 +09:00
eomsangdonandClaude Sonnet 5.5 e8f3cb8a9d fix(639-1): 낡은 시험 둘 고침 · vite 지켜보기에서 코드 아닌 폴더 제외
- test_551_2: 선 그리기 색 읽는 자리 = A00_Common/map/map_route_preview.ts (636-2 가 RecPreview 에서 옮김)
- test_624_2: D03 도구줄에 추천(recommend) 묶음 더해짐(636-2)
- vite.config.ts: server.watch.ignored = tmp · storage · docs · venv · logs · resources/tester · *.log

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 12:49:12 +09:00
eomsangdonandClaude Opus 5.5 8d3f854daf feat(635-4): B05 예정노선 수정에 흐린 다른 세트 겹 · 이음점 붙기 · 벗어남 안내 배선(요구 546 · 547)
- ExpectedDraw — plan-context 겹 읽음 · 그림(켬끔 상관없이 보기만) · 키트 snaps = 남 세트 끝 · 그리기 칸 벗어남 안내 한 줄 · 검증 창구 others
- 시험 test_635_4 배선 줄 대조 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:37:46 +09:00
eomsangdon 946304e9bb Merge remote-tracking branch 'origin/dev' into sub_laptop_1 2026-11-01 12:30:21 +09:00
eomsangdon 021dca8bae Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-11-01 12:29:17 +09:00
eomsangdonandClaude Opus 5.5 a83db2c6f8 feat(635-3): B05 왼쪽 위치 · 그리기 칸(예정노선 키트) · 예정노선 수정 켬끔 · 받은 노선 비교 · 받은 노선으로 · 켬 때 어둡게(요구 546)
- ExpectedDraw = 키트 createPlanKit 「b05」 한 벌 — 위치 칸(찾기 → 지도 이동만) · 그리기 칸 머리 「예정노선 수정」 · 받은 노선 켬끔 · 길이 비교 한 줄 · [받은 노선으로](받은 꼭짓점 다시 추적 + 초안 expected [] · 한 걸음)
- 고칠 선 = 받은 원본 꼭짓점(GET /route/plan/source · 635-2) · 고친 뒤엔 고친 꼭짓점
- 새 B05_Profile_UI_Panel_Plan — registerPanelSection order 1 · 2 빈 자리 + 편집기가 채움
- 켬 = 발자국 ~ 구조물 마커 불투명 --draw-map-dim(0.35 · 새 토큰) · 등고 높이값 · 예정노선 그림은 그대로 · 장면 overAll = { paint, dim }
- 도구줄 「예정노선 변경」 · 「예정노선 원본」 걷음(map_toolbar · map_tools · Chrome · css)
- RouteEdit 683 → 682 줄(주석 한 줄 걷고 frame 한 줄) · Render 648 → 653
- 시험: 새 test_635_3 · 626_3 키트 · 아이콘 걷음 대조로 고침 · 551_2 불투명 값(색 아님) 받음 · 568_1 · 634_2 아이콘 목록에서 둘 뺌(걷음)
- 검증_sub6 B: 픽셀 불투명 255 → 89 · 끌기 · 지움 · 다시 긋기 · 받은 노선으로 · 저장 · 다시 열기 · 초기화 = 받은 노선 · 지킴 지문 110/110 · 되돌림

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:28:43 +09:00
eomsangdonandClaude Opus 5.5 23059f598b feat(635-4): B05 흐린 다른 세트 겹 · 이음점 셈 · 키트 붙기 칸(요구 546 · 547)
- 새 logic/B05_Profile_PlanContext.ts — 흐릴 세트(내 것 빼고) · 이음점(남 세트 끝) · 받을 때 이어진 이음점 · 벗어남 안내 · 이름표 자리
- 새 B05_Profile_UI_PlanContext.ts — plan-context 실시간 읽기 · 옅은 선 + 세트 이름표 · 누름 칸 없음(보기만)
- 키트 · 긋기 · 고르기 snaps 덧칸 — 긋기 · 노드 끌기가 이음점에 붙기만
- 기능 표 others 줄(B05 보기만)
- 시험 test_635_4(6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:23:28 +09:00
eomsangdon 499f2fb7ec Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-01 12:12:08 +09:00
eomsangdonandClaude Opus 5.5 ad428e4b4d fix(638-3): [잇기] 판 · 메뉴로 시작하면 지도 고리를 바로 그림
- startJoin 뒤 onSelect(페이지 refresh · map.redraw) — 마우스가 움직이기 전엔 고리가 안 보이던 것

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:11:21 +09:00
eomsangdon 23b136a6c0 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-01 12:06:01 +09:00
eomsangdon 0b8a2b734f Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-01 12:05:55 +09:00
eomsangdon e6ea6db51d auto: 2026-11-01 12:05 (ESD_LAPTOP) 2026-11-01 12:05:30 +09:00
eomsangdon 4432c18816 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-11-01 12:04:19 +09:00
eomsangdonandClaude Opus 5.5 a7abcd6c5f fix(636-2): 가이드 선 글 자리 = 끝 지점 핀 아래 · 화면 오른쪽 반이면 오른쪽 맞춤(지점 이름 · 화면 끝에 안 걸림)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:03:48 +09:00
eomsangdonandClaude Opus 5.5 eba5223356 feat(638-3): 예정노선 노선 세트 화면 — 속성 판 세트 칸 · 연도 밑 세트 줄 · 이름표 「2027-1」 · [잇기] · 저장 뒤 끊김 경고 · D04 세트 줄(요구 547 · 548)
- 속성 판 · 왼쪽 칸 세트 고름 = setChoices(있는 세트 + 해마다 새 세트) → assignSet(토막 · 구역 · 구간 고름 모두) · 옛 setPiecesYear · setLineYear 자리 갈음
- 왼쪽 해 줄 밑 세트 줄(길이 · 구간 수 · 덩이 수 = 마지막 서버 셈) · 같은 세트 고름 · 되돌리기 뒤 길이 글 옛 값 흠 고침(열쇠에 구간 꼴)
- 지도 이름표 「2027-1」 · 속 빈 마름모 = 같은 세트 · 마커 이름 「구간 나눔」
- [잇기] = 선 끝 노드 속성 판 · 우클릭 → 다른 선 끝 누름 = joinEnds 한 걸음 · Esc 그만
- [저장] 뒤 derived.sets parts > 1 · branch = 알림 「2027-1 이 2 덩이 · 빈틈 35.0 m」(Group.setWarnings · setGapM)
- D04 표 첫 칸 = 세트 · 선 · 덩이 · 갈래 + 확정 막힘 알림 띠
- 기능 표 set(B05 보기만) · join(B05 숨김) 줄 · 시험 638_group 빈틈 · 626_3 표 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:00:21 +09:00
eomsangdonandClaude Opus 5.5 932bab4248 fix(예정노선): 634-3 한 행 자리 클래스 d03-plan__icons — 그리기 칸 d03-plan__row 와 겹침 풂
- 한 행 자리 CSS(absolute 위 · 좌 · 우)가 그리기 칸 줄(긋기 · 연도 구역 마커)에도 걸려 머리 위로 뜸 → 이름 바꿈
- 시험 553_4 · 625_draw 클래스 이름 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:57:41 +09:00
eomsangdonandClaude Opus 5.5 527e50180b feat(636-2): 추천노선 메뉴 · 선 그리기 공용(map_route_preview) · 예정노선 가이드 선(지점 잇기 · 20 m · ±14 % · 급경사 문턱 = 범위 둘째 경계 · 보기만)(요구 546)
- B05 추천 미리보기 = 공용 메뉴 · 그리기 부름(저장본 넷 · 동작 그대로)
- 도구 표 recommend 줄 = 두 페이지
- test_626_1 = 메뉴 검사를 공용 파일로 옮김(B05 파일이 createPickMenu 를 직접 안 부름)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:55:21 +09:00
eomsangdon db99c46a25 Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-11-01 11:54:21 +09:00
eomsangdonandClaude Opus 5.5 b3dcb73784 feat(637-3): B05 지형 칩 옆 [LAS 올리기] · 진행 서클 · 확인창 → 설계 지형 바꿈 · 공용 올리기 부품 map_lidar_upload(예정노선 같은 단추 · 흐름) · 갈음 때 안 지은 라이다 필터 파일 · 행 지움(요구 546)
- 공용 A00_Common/map/map_lidar_upload — 파일 고름 · POST multipart · 1 초 묻기 · 서클 · 끝난 뒤 일(주소 · 진행 묻기만 인자)
- 예정노선 UI_LidarUpload = 부품 부르기(화면 · 동작 그대로) · 안 쓰는 uploadPlanLidar 걷음
- B05 UI_RouteEdit_Terrain — 칩 · 단추 · 서클 한 칸 · 끝나면 확인창 「설계 지형을 라이다로 바꿈?」(이미 라이다 = force 다시 셈) · covered 거짓 = 바꿈 막고 알림 · 들어올 때 도는 일 이어 봄
- common_util_lidar_upload.drop_stale_filters — 갈음 뒤 안 지은 라이다 필터(옛 LAS) 모델 · 등고선 · 마스크 · 지면점 · manifest 줄 지움 · save_models drop = 그 행만(확정 행 남김)
- 시험 test_637_lidar(필터 지움 · 행 지움 · B05 화면) · test_624_4b(올리기 = 공용 부품으로 옮김)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:53:48 +09:00
eomsangdonandClaude Opus 5.5 6a67384ff9 knowledge(요구): 요구서 546 3장 — 가이드 선 걸음 · 기울기 = 고정 20 m · ±14 %(그리기 칸 값과 따로)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:52:40 +09:00
eomsangdon 205669f439 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-11-01 11:50:43 +09:00
eomsangdonandClaude Opus 5.5 f48be66c9a fix(637-4): [초기화] 때 설계 지형도 초기값으로 — 초기값에 stage 1 지형 고름 담음 · 되돌릴 때 stage 1 확정값 · CONFIRMED 모델(노선 행이 가리키는 것) 함께 · 옛 초기값 = 지형 그대로 + 로그 한 줄(요구 546)
- 재현(검증_sub4 B · 라이다 → 수치지형도 → [초기화]): stage 1 = 수치지형도 · 노선 행 = 라이다 모델 · 횡단 지반선 대 stage 1 지형 평균 3.78 m · 최대 13.67 m
- 고친 뒤 같은 길(API · ORCA 칩 → [초기화]): 세 자리 라이다 · 어긋남 0
- las_selection · 등고선 간격 등 지형 밖 칸 안 건드림 · stage 1 행 없으면 안 씀
- 시험 test_637_4_reset_terrain 새로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:50:22 +09:00
eomsangdon 1ee98dd8ca Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_6 2026-11-01 11:49:42 +09:00
eomsangdonandClaude Opus 5.5 cd0f68946b fix(부품): 묶음 단추 켠 수 원형 숫자 걷음(요구 551)
- createPickMenu 여럿 = 아이콘 + ▾ · 켠 표시는 단추 눌림(하나라도 켜면) 그대로
- 배지 count accent 덧 · 메뉴 css 배지 줄 걷음 · 견본 · 겹 줄 머리 글 맞춤
- 시험 634_1 고침 — 배지 대신 「배지 없음 · 눌림 = 켠 줄 있음」을 잡음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:49:26 +09:00
eomsangdon 4eb4fba5d7 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-11-01 11:49:17 +09:00
eomsangdonandClaude Opus 5.5 def02db1fe knowledge(요구): 요구서 546 3장 — 묶음 단추 켠 수 원형 숫자 없음(요구 551)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:49:05 +09:00
eomsangdon ba82499843 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-11-01 11:48:25 +09:00
eomsangdon 0e4ce3cec1 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_3' into sub_laptop_6 2026-11-01 11:47:37 +09:00
eomsangdonandClaude Opus 5.5 067dc7d461 knowledge(요구): 요구 551 원문 기록 — 지도 아이콘 묶음 단추 켠 수 원형 숫자 걷음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:47:15 +09:00
eomsangdonandClaude Opus 5.5 e70201a626 feat(635-2): 받은 세트 문맥 GET plan-context · 원본 꼭짓점 GET route/plan/source · 다시 추적 전 조밀화 = 체인 초기선 거리 0(요구 546 · 547)
- 세트 연결 = 638-1 그대로(B 하나 = 세트 하나 · 열쇠만 세트)
- 새 D04_PlanConfirm_Context — 같은 예정노선 세트 모두 · 내 세트 · 연도 · 임도 좌표계로 pyproj 변환 · 업체 이름 없음 · 보기 전용
- 새 B05_Profile_Router_PlanSource — 원본 꼭짓점 = 범위 + 지표면 트림 · 조밀화만 뺌(Router_Replan 안 늘림 · 덧칸 대신 새 길)
- RouteFollow 추적 전 체인과 같은 3.6 m 조밀화(브레인 허락 · 옛 촘촘 입력은 그대로 = 626-2 같음)
- 실측 검증_sub3: 원본 64 점 → follow = 초기선 181 점 거리 0 · 파일 안 바뀜

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:46:53 +09:00
eomsangdon e478fe9067 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-11-01 11:44:11 +09:00
eomsangdonandClaude Opus 5.5 ae6eace1ad refactor(635-1): 예정노선 키트 · 기능 표 · 지도 입력 한 함수 — oneLine · 가짜 값 걷음(요구 546)
- 새 D03_PlanAnalysis_UI_Features.ts — 노선 그리기 기능 표(요구서 546 4장 · pages d03 · b05 = use · view · hide · 까닭) · featureUse · featureOn · featureShown
- 새 D03_PlanAnalysis_UI_Kit.ts — createPlanKit(부품 엮기 · 왼쪽 칸 위치 · 지점 · 그리기 · 구역·연도 · 겹 차례 · 키 · sync) · bindPlanInput(누름 · 맴돎 · 놓음 · 우클릭 · 연도 마커 끌어 놓기 · capture = B05 켠 동안 먼저 받음)
- 예정노선 UI_Page 680 → 620 줄 · 키트로 엮음(화면 · 저장 · 되돌리기 그대로) · UI_Map 지도 입력 줄 → map.input
- UI_Draw · UI_Menu · UI_Props · UI_DrawPanel — oneLine 플래그 → 기능 표 page 읽기 · 지점 · 경위도(lonLat) · 연도 시작 = 없으면 숨김(빈 지점 · epsg 0 가짜 값 걷음) · oneLineBlock → lineLimitBlock
- B05 ExpectedDraw — page: "b05" 넘김 · NO_POINTS · 가짜 값 걷음 · 지도 입력 = bindPlanInput capture(동작 그대로) · 속성 판 경위도 빈칸 → 칸 숨김
- 시험 고침: test_626_3(oneLine 글 대조 → 기능 표 · 키트 capture 대조) · test_475(위치 칸 서클 = 키트 site.title)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:42:49 +09:00
eomsangdon d9cc2144f2 Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_5 2026-11-01 11:42:16 +09:00
eomsangdonandClaude Opus 5.5 61733d064d feat(지도): 634-2 · 634-3 아이콘 표 두 장 · 묶음 메뉴 · 한 행 배선 · 호버 = 표(요구 546)
- 겹 표(map_layer_list) = 그룹1 22 줄 표 차례 · title = 호버제목 · group(수계망도 · 도로 · 토지·건물 · 제한·재해) · pages · 숨김 표 걷음 · 도로300m 한 줄(「도로」 묶음 · 기능 남김)
- 겹 줄(map_layer_bar) = 단독 넷 | 묶음 넷(여럿 고르기 메뉴 · 켠 수 배지 · 안 닫힘) | 그래픽 카드 · sync 가 단추 · 메뉴 줄 막힘 · 글을 맞춤(why = 페이지 까닭)
- 도구 표(map_toolbar) = 그룹2 17 줄 title · mapToolGroups 가 글을 표로 맞춤 · 기본 단추 생성 · 지형 칩 막힘 「수치지형도/라이다 — 까닭」
- 띠 = 한 자료 · 겹 둘(GL fillEdge 거름 · 옛 캔버스 · 범례 줄 따로) · B05 띠 받기(D03 과 같은 서버 길 · bandCollection 공용)
- D03 = 겹 줄 · 도구줄 한 행(createMapToolRow) · 222px 기댐 · 줄바꿈 CSS 걷음 · 측점 · 기울기 호버 덮어쓰기 걷음 · LAS 올리기 호버 걷음
- B05 = 세로 두 줄 → 한 행 · 호버 덮어쓰기 걷음(도구줄 · 1m 등고선 · 3D · 추천) · 겹 막힘 querySelector 덮어쓰기 → 공용 sync · 임도(업로드) 보임 + 「받은 임도 자료 없음」 막힘 · 스냅 간격 날 select → 고르기 메뉴(하나)
- 켬끔 기억 열쇠 · 측점 · 기울기 처음 값 · 그래픽 카드 취향 열쇠 둘 그대로
- 낡은 시험 고침(568_1 · 622_2 · 624_2 · 624_3 · 553_4 · 625_draw · b05_routeedit_initial · 626_3 · 390_4 · 346_2 · 353_1 · 581_2 · 584_1 · 584_3) — 표 꼴 row() · 호버 글 = 표 · 한 행 · 고르기 메뉴로 바뀐 글을 잡음 · 새 지킴 634_2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:40:26 +09:00
eomsangdon 480c325934 Merge remote-tracking branch 'origin/dev' into sub_laptop_6 2026-11-01 11:36:14 +09:00
eomsangdonandClaude Opus 5.5 3753a5cdb4 feat(638-2): 예정노선 Api 꼴 — derived.pieces[].set_key(638-1 서버 꼴 0b738b20e)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:36:03 +09:00
eomsangdon fd99b91b93 Merge remote-tracking branch 'origin/sub_laptop_3' into sub_laptop_4 2026-11-01 11:35:52 +09:00
eomsangdonandClaude Opus 5.5 0b738b20e4 feat(638-1): 예정노선 노선 세트(연도-번호) 서버 — 조각 set · 같은 세트 합침 · derived.sets 덩이 · 갈래 · 판 2 · 업체 열쇠 세트 · 확정 막음 · 연결 = 세트 한 줄(요구 547 · 548)
- 파일 pieces[].set · 판 2(옛 서버는 409 로 세트를 못 덮음) · 옛 조각 = 그 해 선 · 거리 차례 번호
- normalize — 같은 선 이웃 같은 세트 하나(앞 id · 마크 걷음) · TS Group.ts 짝 · 거울 사례 = sub6 fixtures 그대로
- derived.sets[] {key, year, no, parts, branch, length_m, pieces}
- D04 단위 = 세트(이름 「2027-1」 · 폴더 「2027-1_<선>」) · 덩이 2 이상 · 갈래 세트는 확정 409
- 업체 열쇠 = 세트 · 옛 조각 id 풀어 봄 · D03 [저장] 때 세트 열쇠로 · 사라진 세트 409
- 연결 = 세트 이은 한 줄 · 아니면 409 · D01 받은 목록 · B01 홈 = 세트 열쇠
- 낡은 시험 고침: 530_2 · 590_1(판 2) · 531_3 · 531_d04 · 563_2(조각 이름 → 세트 이름)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:34:39 +09:00
eomsangdon 690524d328 Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into main_laptop_1 2026-11-01 11:34:00 +09:00
eomsangdonandClaude Opus 5.5 85dade9a21 feat(637-2): B05 라이다 나중 올리기 창구 — POST · GET /route/lidar(뿌리 B04_PreProcess/models 짓기 · 입력 lidar_las · 갈음 · 넣기/고치기만 keep · 수치지형도 B = las_selection 적기 · 노선 2 m 덮임 covered · 저장 잠금) · 지형 바꿈 force(같은 지형 다시 셈) · main 등록 · 시험(요구 546)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:32:57 +09:00
eomsangdonandClaude Opus 5.5 e2da944c78 feat(636-1): 예정노선 가이드 추천선 셈 — 2 m 격자 A*(요구 546)
- 새 A00_Common/map/logic/map_guide_route.ts — 지점 차례 구간마다 A* · 20 m 걸음 56 방향 · ±14 % 막음 · 급경사 35°+ · 기울기 · 꺾임 벌점 · 가중 어림 × 3 · 상자 두 점 + 여유(끝에 닿았을 때만 두 배 한 번) · 펼침 30 만 + 450 ms 상한 · 첫 · 끝 걸음 정확 · 1 m 격자 2 칸 솎음 · 못 닿음 = 가장 가까이 간 데까지
- 화면 가이드 전용 · 파이썬 짝 없음 · 저장 없음
- 지킴 시험 test_636_guide_route — 평지 · 지그재그 · 못 닿음 · 지점 셋 · 급경사 벌점 · 1 km · 솎음 · 지형 밖

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:32:40 +09:00
eomsangdonandClaude Opus 5.5 83f3bc0a5c feat(638-2): 예정노선 노선 세트 화면 셈 — 세트 지정 · 끝에서 나눔 · 이웃 합침 · 고를 세트 · [잇기](요구 547 · 548)
- 새 logic/D03_PlanAnalysis_Group.ts: assignSet(고른 구간 끝 나눔 → 세트 → 같은 선 이웃 같은 세트 하나 · 앞 id) · mergeSets(서버 Network.derive 짝) · setChoices(있는 세트 + 해마다 새 번호) · joinEnds(닿음 바로 · 떨어짐 곧은 토막 덧 = 앞 선 세트)
- Plan.ts 세 곳: PlanPiece.set · 새 선 처음 세트(사업연도 시작 해 새 번호 · 갈래 = 붙은 자리 세트 · 옛 꼴 = 그 해 최댓값 + 1) · 이음 합침 비교 = 세트 · 661 줄 그대로
- Select.ts 끌어 고르기 시험 조각에 set 칸(타입 맞춤)
- Api.ts 꼴: derived.sets(PlanSetView · 638-1 서버 셈)
- 시험 test_638_group.py · 거울 사례 fixtures/d03_638_set_cases.json(sub3 638-1 과 같은 파일)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:31:17 +09:00
eomsangdonandClaude Opus 5.5 f6708c6818 refactor(637-1): 예정노선 LAS 올리기 · 짓기 공용 모듈 common_util_lidar_upload — 이름 검사 · 좌표계 · 옮겨 놓기 · 받기 · 갈음 · 짓기(maps=False) · 뒤 일 · 진행 기록 · DB 넣기/고치기만(keep = 갈음 때 id · 확정 · 입력 연결 살림) · 예정노선은 그걸 부름(동작 · 응답 그대로) · 지킴 시험 test_637_lidar(요구 546)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:28:57 +09:00
eomsangdonandClaude Opus 5.5 2265ac4f7c feat(부품): 634-1 여럿 고르기 메뉴 · 한 행 두 영역 · 막힘 글 · 새 아이콘(요구 546)
- createPickMenu multi — menuitemcheckbox · 눌러도 안 닫힘 · values() · 줄 icon · disabled · title · setItem · 단추 ▾ + 켠 수 배지(0 = 없음) · 하나라도 켜면 눌림 · 밀기 · 창 크기 바뀌면 닫힘 · offLabel 없으면 끔 줄 · 눌림 없음
- createMapToolRow — 그룹1 왼쪽(행 반까지) · 그룹2 오른쪽 · 영역마다 min-width 0 + 자기 가로 밀기 · [?] 판은 행 아래
- mapToolTip — 평소 제목만 · 막힘 때만 「제목 — 까닭」 · MAP_TOOLS 글 = 표 호버제목
- 아이콘 — 묶음 넷(streams · signpost · parcelHouse · ban) · 겹친 셋 대신(contours · cutFill · lineament) · 스냅 간격 spacing
- 배지 count · accent 덧 · 부품 모음 견본 「고르기 메뉴(하나 · 여럿)」 · 지도 도구줄 한 행 견본
- 시험 568_1 고침 — 예정노선 둘 줄이 한 줄 꼴이 되어 목록에 잡힘 · 새 지킴 634_1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:26:52 +09:00
eomsangdonandClaude Opus 5.5 bbe3a05201 knowledge(요구): 요구서 546 8장 노선 세트(연도-번호) · 조사 547 — 받기 단위 = 세트 · 끊김 경고 · 잇기 · PLAN 638 · 635-2 세트 연결 · 착수(요구 549)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:17:39 +09:00
eomsangdonandClaude Opus 5.5 bea1cd92b9 knowledge(요구): 요구 550 원문 기록 — 착수 뒤 브레인 메모장 압축(내역서 · 평면도 몫은 남김)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:11:38 +09:00
eomsangdonandClaude Opus 5.5 f06ae457c1 knowledge(요구): 요구 549 원문 기록 — 조사 끝나면 요구서 · 계획서 고치고 착수
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:09:29 +09:00
eomsangdonandClaude Opus 5.5 74536f1c86 knowledge(요구): 요구 548 원문 기록 — 중복 연도 없음 = 한 선 한 연도 · 같은 연도 안 선 세트 1 · 2 구분 · 같은 번호끼리 같은 노선
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:08:37 +09:00
eomsangdonandClaude Opus 5.5 91654d5041 knowledge(요구): 요구 547 원문 기록 — 예정노선 노선 그룹 · 연도 정의(지운 뒤 같은 연도 · 골라 연도 지정 · 저장 때 끊김 경고 · 병합) · 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:05:29 +09:00
eomsangdonandClaude Opus 5.5 7a28fb8bd0 knowledge(요구): 요구서 546 · 조사 셋 — 지도 아이콘 그룹화 · 노선 그리기 한 벌 · 예정노선 가이드 추천선 · B05 라이다 · PLAN 634~637 승인 대기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:27:40 +09:00
eomsangdonandClaude Opus 5.5 7b0eb34fb0 knowledge(요구): 요구 546 원문 기록 — B05 평면 · 예정노선 지도 아이콘 그룹화 · 로직 공용화 · 작업 전 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:07:25 +09:00
eomsangdonandClaude Opus 5.5 d6805f41f6 knowledge(계획): PLAN 632 이관 — 데스크탑 메인 venv 라이브러리 이미 깔림 확인(632-2) · PLAN 에는 621 만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHDcYVshd8kicU5oARJ
2026-11-01 08:46:00 +09:00
eomsangdon 346a70d9bd auto: 2026-11-01 00:30 (EOMSANGDON-HOME) 2026-11-01 00:30:44 +09:00
eomsangdonandClaude Opus 5.5 5518a5bba6 knowledge(계획): PLAN 607 · 623~626 · 625-5 · 631 · 633 이관 — 끝난 절 docs/raw/plans 로 · PLAN 에는 621 · 632 만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHDcYVshd8kicU5oARJ
2026-11-01 00:28:52 +09:00
eomsangdonandClaude Opus 5.5 331a12b267 chore(설정): 시놀로지 전달용 임시 폴더 /_sync_data_forest/ git 밖(요구 544)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHDcYVshd8kicU5oARJ
2026-10-31 23:42:20 +09:00
eomsangdonandClaude Opus 5.5 defb98dd1c knowledge(요구): 요구 543 · 544 원문 기록 — 1 m 유지 · 큰 산림 자료는 git 대신 시놀로지로 · 올리지 못한 데이터 커밋 되돌림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHDcYVshd8kicU5oARJ
2026-10-31 23:41:28 +09:00
eomsangdon 18bfd28412 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-31 23:40:56 +09:00
eomsangdon 3cabb6e5f0 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-10-31 22:52:54 +09:00
eomsangdonandClaude Opus 5.5 a1092b6c7c fix(633): 격자 칸 = 좌표 간격 — B05 급경사 · 음영 · 0차곡 칸 크기 틀림 고침(요구 538)
- 공용 grid_cell_m — 적힌 칸과 좌표 간격이 1 % 넘게 다르면 좌표 간격 · 로그 한 줄
- D03 급경사 · 음영 · 0차곡(상자 · DEM 길) · B07 도곽 음영이 공용 함수로
- 판 올림 — 급경사 · 음영 2 → 3 · 0차곡 DEM 길 따로 판 4
- B04 평활 격자는 넓힌 실제 칸을 적음
- 검증_sub2 B 평활: 급경사 중앙 146 → 67 % · 빨강 89.4 → 26.7 % · 0차곡 21 가닥 989 m → 99 가닥 4,110 m · D · B 수치지형도 그림 전후 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHXDsevXBHePbAoqUv
2026-10-31 22:52:53 +09:00
eomsangdonandClaude Sonnet 5.5 e3eaacb9a7 fix(D03 그림): − 노드 색 진한 파랑 #3b6bff — 민트 노선 선과 구분(대비 3.18 : 1) (PLAN 625-5 · 요구 540)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHDqStFX129bJDAJXB1
2026-10-31 22:52:12 +09:00
eomsangdonandClaude Sonnet 5.5 f67d868d85 fix(산림 조각): 원자료 지문을 경로 · 크기로 바꿈 — 수정 시각 뺌 (PLAN 632-0 · 요구 542)
git 으로 옮겨도 지문이 같아 데스크탑에서 올린 색인 · 조각을 그대로 씀.
새 지문: forest_road 3f4fcf3e · eco_forest 1d125434 · forest_soil a9a02742
시험: 같은 내용 · 다른 시각 = 같은 지문 · 크기 바뀜 = 다른 지문.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHoPiWty5oQce1oAXD
2026-10-31 22:50:32 +09:00
eomsangdonandClaude Opus 5.5 76b262706a knowledge(요구): 요구 542 원문 기록 — 산림 자료 빼지 말고 색인까지 다 올림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:49:05 +09:00
eomsangdon 812f97e88c Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-31 22:46:44 +09:00
eomsangdonandClaude Opus 5.5 1460ca22fe feat(공용 지도 급경사): 범위 설정 창 = 수평 바 — 칸 사이 경계값 한 번 · 칸 눌러 색 · 칠 안 함 칸 · 단계 추가(고른 칸 반으로) · 지움(이웃과 합침) · 8 단계까지 · 같은 값 · 거꾸로 · 빈 값 · 0 · 250 막음 · 새 부품 범위 바 + 부품 모음 줄 · 결과 꼴 · 그림 · 범례 그대로 (PLAN 631 · 요구 537)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHEAjkLLn21sdLiKAkn
2026-10-31 22:46:18 +09:00
eomsangdon 2d30525e9b Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_1' into sub_laptop_2 2026-10-31 22:46:07 +09:00
eomsangdonandClaude Opus 5.5 1f3a880dca knowledge(요구): 요구 538 조사 기록 — B05 급경사 빨강 = 칸 크기 틀림(0.5 대 1.078 m) · 라이다 잔요철
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:45:54 +09:00
eomsangdonandClaude Sonnet 5.5 306a88e964 fix(D03 긋기): 긋는 중 놓인 점이 부채꼴 · 점선 원 위에 보임 · 좁은 폭에서 도구줄 줄바꿈으로 [LAS 올리기] 안 잘림 (PLAN 625-4 · 점검 535-3)
- DrawPaint `paintNetwork` 미리보기(부채꼴 · 원) 먼저 · 놓인 점 그 위
- D03 도구줄 CSS 줄바꿈(공용 한 줄 밀기 · B05 그대로)
- 시험 test_625_draw 순서 · 줄바꿈 지킴 하나 덧

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHDYKk4dyt7bEAQKcVU
2026-10-31 22:45:14 +09:00
eomsangdonandClaude Opus 5.5 0f2445fa89 knowledge(요구): 요구 541 원문 기록 — 산림 자료 원본 zip 도 git 에
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:44:00 +09:00
eomsangdon 821beacc31 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-10-31 22:43:49 +09:00
eomsangdonandClaude Opus 5.5 e250517adf feat(B05 평면): 예정노선 변경 = 예정노선 페이지 긋기 · 고르기 · 메뉴 · 속성 판 그대로(한 선) · 원본 옅은 점선 켬끔 (PLAN 626-3 · 요구 535-1 · 536 · 535-3)
- B05 「예정노선 변경」 켬끔 = D03 createPlanDraw · createPlanEdit · createPlanMenu · createPlanProps 를 한 선 network 로 얹음(같은 함수 · 단추 · 단축키 · 노드 끌기 · 고른 토막 끌어 꺾임점 · Delete · [다시 긋기]) — 갈래 · 둘째 선 · 연도 · 지점은 막음/숨김
- 한 번 고침(긋기 끝 · 놓음 · 지움) → 켠 뒤 첫 고침만 확인창 → `/route/follow` → 편집기 한 걸음 · 다 지우면 새로 그을 때까지 평면 그대로
- 도구줄 「예정노선(원본)」 켬끔 — 파일입력 예정노선(안 덮음)을 옅은 점선으로(고친 것과 다를 때만)
- D03 덧 인자 `oneLine`(UI_Draw · UI_DrawPanel 연도 마커 · UI_Menu · UI_Props) — 기본값 = 지금 그대로 · 긋기 칸 · 속성 판 꼴 CSS 를 `D03_PlanAnalysis_UI_DrawParts.css` 로 옮겨 그 TS 가 불러옴(D03 꼴 같음)
- 옛 자체 긋기 셈 `logic/B05_Profile_ExpectedDraw.ts` 지움(D03 셈으로 갈음) · 시험 test_626_3 다시 씀(5)
- 낡은 글 고침 — 설계 지형 바꿈 = 지형 칩(`/route/design-terrain`) · 「추천 [적용] 때만」 글 걷음(Router_Terrain · Router_Replan · Api_Replan · RouteEdit_Apply · 동작 그대로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:42:36 +09:00
eomsangdonandClaude Opus 5.5 26705681b8 knowledge(요구): 요구 540 원문 기록 — 작은 것까지 고쳐서 전역 깃 싱크
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:39:54 +09:00
eomsangdonandClaude Opus 5.5 f0ed479666 knowledge(요구): 요구 539 원문 기록 — 이번 수정 반영 뒤 전역 깃 싱크
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:39:21 +09:00
eomsangdonandClaude Opus 5.5 74d653b4a3 knowledge(요구): 요구 535-3 점검 기록 — 빠진 요구 셋(B05 예정노선 쪽) · 작은 것
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:36:55 +09:00
eomsangdonandClaude Opus 5.5 a6be7f9691 knowledge(요구): 요구 538 원문 기록 — B05 급경사 색이 수치지형도와 너무 다름 · 범위 바꿔도 거의 빨강 · 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:34:47 +09:00
eomsangdonandClaude Opus 5.5 7d196c4854 knowledge(요구): 요구 537 원문 기록 — 급경사 범위 설정 = 수평 바 · 경계값 · 칸 눌러 색 · 단계 추가
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:31:39 +09:00
eomsangdon 0ec76ade56 Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_4' into main_laptop_1 2026-10-31 22:29:50 +09:00
eomsangdonandClaude Opus 5.5 3de262a4a9 fix(D03 긋기): 놓인 노드 +/− 색 보이게 — 선보다 굵은 부호 색 점 + 검은 테 · 고른 선은 노랑 강조 위에 점 다시 · 손잡이 = 빈 네모 (PLAN 625-3 · 요구 535-2)
까닭 — 놓인 점 반지름 3 px · 테 없음 = − 하늘(#29e0ff)이 노선 민트 선(#3df5c4)에 묻힘 · 고른 선은 흰 네모 손잡이와 노랑 강조(8 px)가 점을 덮음.
- DrawPaint `paintNodes` 떼어 냄(점 5 px · fillCased) · Edit 가 고른 선만 강조 위에 다시 그림(heightAt 인자 덧)
- 시험 test_625_draw 놓인 노드 지킴 하나 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:28:41 +09:00
eomsangdonandClaude Opus 5.5 8bf9156d19 knowledge(요구): 요구 536 원문 기록 — B05 예정노선 변경 활성화 · 선 긋기 컨테이너 가져옴
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:22:57 +09:00
eomsangdonandClaude Opus 5.5 8a7d972ce8 fix(B05 평면): 예정노선 그리기 — 확인창 동안 새 예정노선 보임 · 지형 격자 받은 뒤 미리보기 다시 · 검증 창구 (PLAN 626-3)
- 확인창이 떠 있는 동안 바뀔 예정노선 통째를 그림(묻는 동안 무엇이 바뀌는지)
- 설계 지형 격자를 받기 전 부채꼴이 「자리 없음」으로 남던 것 — 받은 뒤 미리보기 다시 셈
- 지도 돌림을 함수 하나로 받음(RouteEdit 683 줄) · 검증 창구 `expectedProbe`(상태 · 보이는 예정노선 · 화면 px)
- 예정노선 판 자리 = 도구 줄 아래(떠 있는 판 absolute · 높이 100 % 풂)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:20:13 +09:00
eomsangdon b85eb258e4 auto: 2026-10-31 22:19 (ESD_LAPTOP) 2026-10-31 22:19:11 +09:00
eomsangdon 48cf88cc93 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_3' into sub_laptop_1 2026-10-31 22:17:03 +09:00
eomsangdonandClaude Opus 5.5 73d0844720 knowledge(요구): 요구 535 원문 기록 — B05 예정노선 긋기 = 예정노선 페이지와 같음 · 공무원 원본 남김 · 놓인 노드 색 · 빠진 요구 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:16:37 +09:00
eomsangdonandClaude Opus 5.5 a6ef53e8a3 feat(공용 지도 겹): 임도 짐작 걷음 · 그 자리 = 임도망도(전국 국가임도 · route 아이콘 · 범례 「임도망도 간선 · 작업 · 산불」 · 줌 14 부터 연도 · 종류 글) · 올린 임도망도 = 임도(업로드)(위치 칸 단추 · 목록 · 알림 · 서버 글) (PLAN 630 · 요구 532 · 534)
- 열쇠 그대로(nationalRoad · forestRoadFile) — 옛 켬끔 취향 이어짐 · forestRoad 열쇠 · 도로 소로 거름은 B05 자료 셈 이음으로 남김(겹 목록 · 꼴 · 범례만 걷음)
- 기존 도로(road) 겹 · 소스 · 범례 그대로
- 시험 553_4 겹 차례 · 584_3 범례 기대값을 새 이름으로 고침 · 622_2 에 630 · 534 지킴 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:10:58 +09:00
eomsangdonandClaude Opus 5.5 36cde038b1 knowledge(요구): 요구 534 원문 기록 — 임도망도 줌인 글(개설 연도 · 종류) · 코드 일원화 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:08:05 +09:00
eomsangdonandClaude Opus 5.5 c932a18479 knowledge(요구): 요구 533 원문 기록 — 기존 도로 겹 유지
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:04:26 +09:00
eomsangdon a80367a2c2 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_3 2026-10-31 22:03:56 +09:00
eomsangdonandClaude Opus 5.5 893e4e2d86 knowledge(요구): 요구 532 원문 기록 — 임도 짐작 걷고 임도망도 · 올린 임도망도 = 임도(업로드) · 임도망도 칸 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 22:03:10 +09:00
eomsangdonandClaude Opus 5.5 84e9151b7c feat(B05 평면): 예정노선 그리기 — 새로 긋기 · 구간 다시 긋기 · 지움 → 평면선 다시 추적 = 편집기 한 걸음 → [저장] (PLAN 626-3 · 요구 530 · 531 · D3 · D4 · D9 · D10)
- 도구줄 「예정노선 그리기」 칸(공용 표 b05 · 연필 아이콘) → 떠 있는 판(단추 셋 + 공용 방식 칸) · 고치는 예정노선 맨 위 · 판 켠 동안 캔버스 누름을 먼저 받음(계획선 · 구조물 · 등고선 고르기 · Shift 재기 · 노드 넣기 · 지움 막음 · 가운데 끌기 · 휠 그대로)
- 긋기 = D03 과 같은 방식 칸 · 부채꼴 · +/− 색 · Shift 사이 기울기 · Tab · Enter · Esc · Backspace(셈 `logic/B05_Profile_ExpectedDraw` · 그림 D03 `paintPreview` 그대로)
- 끝 → 확인창(직접 고친 평면 버림) → `POST /route/follow`(저장 0) → `restoreSnapshot(…, true)` 한 걸음(되돌리기 됨) · 지움 = 파일입력 예정노선으로 다시 추적
- 걸음 사진 · 노선 초안에 `expected`(같은 선 판정 · 세션 되살림 · 페이지 되돌리기) · [저장] = 손 편집과 같은 길 + 초안 `expected`(null 안 실음 · [] 지움) · 다시 열면 `expected_edited`
- `/route/replan` · `as_initial` · `expected_route.csv` · 초기값 · 추천 저장본 안 건드림 · 시험 test_626_3_expected_edit(7)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:58:15 +09:00
eomsangdonandClaude Opus 5.5 1ed836abfb chore(계획): PLAN 612 · 618 이관 — 전국 미리 계산 12,904 도엽 끝 · 두 PC 나눠 돌림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 21:56:14 +09:00
eomsangdonandClaude Opus 5.5 62a8ee362e refactor(B05 평면 · 지도 공용): RouteEdit 마우스 배선 · 재기 한 벌 떼어냄 · 긋기 방식 칸 공용 자리로 (PLAN 626-3 · 요구 530 · 531)
- B05_Profile_UI_RouteEdit.ts 684 → 655 줄 — 두 번 클릭 삽입 · 오른쪽 클릭 삭제 · 지도 이동 · 휠 = 새 `_RouteEdit_Mouse.ts` · 재기 도구 + 단추 한 벌 = `_Measure.ts` `createRouteMeasure` (동작 그대로)
- 긋기 방식 칸(`createDrawKindFields` · 기본값 · 이름) = 새 `A00_Common/map/map_draw_kind.ts` · `.css` — D03 DrawPanel 은 다시 내보냄(D03 화면 · 동작 같음 · 칸 꼴 선택자만 공용 이름)
- D03 `paintPreview` · `planLabel` 받는 지도 꼴을 쓰는 칸만(Pick)으로 — B05 평면이 그대로 부름
- 시험 test_553_4 — 방식 칸 대조를 공용 파일로 옮김(낡은 자리 대조)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:51:58 +09:00
eomsangdonandClaude Opus 5.5 48554430e4 chore(계획): PLAN 612 · 628 · 629 이관 — 전국 미리 계산 끝(지형 · 0차곡 · 0차곡 선 12,904 · 실패 0) · 넓은 줌 조각 · 낡은 시험
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 21:48:49 +09:00
eomsangdonandClaude Sonnet 5.5 dec08b1eb5 test(629-1): test_298_3_save_shape 6 오류 고침 — 헬퍼에 design_pipeline_page 가짜 더함
까닭: b92fa06f3(10-28, PLAN 498-2)가 design_save.ts 에 routeGradeTouch 불러오기를 더했는데 헬퍼 가짜가 빠짐 → stub 없음 오류 6 건. 코드 의도대로 바뀐 것 = 낡은 시험. 가짜값만 고침(B 코드 안 고침).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 21:47:27 +09:00
eomsangdonandClaude Opus 5.5 3aac80765d chore(계획): PLAN 701~708 이관 — 내역서 개편(한시 브레인 sub8 끝)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 21:36:26 +09:00
eomsangdon 63ea2c181d Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_4 2026-10-31 21:35:52 +09:00
eomsangdonandClaude Opus 5.5 d05e36631b knowledge(요구): 내역 11 원문 기록 — 한시 브레인 업무 종료
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:35:23 +09:00
eomsangdon a39ab266d6 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-31 21:34:14 +09:00
eomsangdon 2b02b0dfd0 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-31 21:34:12 +09:00
eomsangdonandClaude Opus 5.5 99ec4b8efe feat(B05 평면 지도): 공용 도구줄 표 · 겹 줄 · 범례 · 지형 칩 붙임 · 노선추천 = 도구줄 미리보기(넷 · 하나만 · 끔) · [적용] · 추천 모달 · 좌측 칸 걷음 · 지형 바꿈 = 지형 칩 (PLAN 624-3 · 626-1 · 요구 530 · 531)
- 624-3 도구줄 = 공용 표 「b05」 · 아래 줄 = 공용 겹 줄(띠 · 올린 임도망도 숨김 · 처음 = 등고선 · 하천) · 범례 = 공용 legendRows(상태 글판 위)
- 노선 띠 300 m 자르기 = B05 자기 칸(등고선 · 하천 · 1 m)만 · 그 밖 겹 = 안 자르는 둘째 GL 칸(맨 밑 · 켠 겹 있을 때만 · GL 만) · 등고선 끔 = 등고 글도 숨김
- 626-1 추천 보기 = 도구줄 아이콘 + 아래 판(영선 · 토공 최소 · 기울기 최소 · 비중 계산 · 끔) — route_recommend 저장본(지금 설계 지형) 선만 · 다시 셈 · 쓰기 없음
- 지형 칩 → 확인창 → 새 POST /route/design-terrain(설계 지형만 바꿈 · 노선 저장 체인 다시 · 노선 · 초기값 · 추천 저장본 그대로 · 잠금 · 실패 되돌림)
- 걷음 — 추천 모달(_Recommend · _Help · _Map · CSS) · 좌측 「노선 추천」 칸 · saveRecommendedRoute · UI_RouteReset(서버 as_initial 길 · 시험은 남김)
- 부품 상자 — createPickMenu(아이콘 고르기 메뉴 · 판 = body fixed · ✓)
- 낡은 시험 고침 · 지움 — 모달 · 좌측 칸 · [적용] 화면 배선을 보던 시험(283 · 346_3 · 349 · 364_4 · 383_1 · 390_5 · 418 · 421 · 421_2 · 476_2 · 509_2 · 509_5 · 551_2 · 600_3 · b05_routeedit_initial · design_view_parity · modal_top · 133_1 · 133_2) · test_508_2 · helper_283 지움(모달 전용)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:30:49 +09:00
eomsangdon af6e921440 Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_8 2026-10-31 21:28:42 +09:00
eomsangdonandClaude Opus 5.5 472bf98ba2 feat(B09 원가계산서 · 708-2): 현행 비목 다섯 셈 — 퇴직공제 · 임금채권 · 석면 · 하도급 요율 셈 · 공사이행보증 금액 손입력
- 셈 짝(Calc_Total py ↔ ts) — STmate 일반형식 식 · 자리(지식DB 12 · 35 분석) · 경비 묶음 안 · 나.소계에 듦
  퇴직공제부금비 = 직접노무비 × 율 · 임금채권 · 석면 = (직노 + 간노) × 율 · 하도급 = 순공사비 × 율 ·
  공사이행보증 = 금액 직접 입력(performance_fee) · 자동 요율 없음 · 요율 빈칸 = 해당 없음(0)
- 요율 출처 「불러옴」 = rates_imported(707-2 불러오기가 실음 · 요율 고치면 사용자)
- 707-2 불러오기 — 「셈 없음」 알림 뺌 · 공사이행보증 줄 금액 = 손입력 칸
- 총괄 · 원가계산서 탭 · 인쇄 미리보기 = 같은 줄 짜기
- 낡은 시험 고침 — 현행 비목 기대값 빈칸 → 0 · 퇴직공제 요율 칸 고침 가능 · 거창 총공사비 = 엑셀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:27:40 +09:00
eomsangdon d66b54cb0c Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_8' into main_laptop_1 2026-10-31 21:16:53 +09:00
eomsangdonandClaude Opus 5.5 92fdf334b2 feat(D03): 예정노선 지형 칩 라이다 · LAS 올리기 — 칩 바꾸면 급경사 · 음영 · 0차곡 · 높이 다시 받음 (PLAN 624-4 화면 · 요구 530 · 531)
- 새 D03_PlanAnalysis_UI_LidarUpload — 공용 칩(안 고침) + 라이다 없으면 [LAS 올리기](부품 단추 · LAS · LAZ · .prj) · 진행 = 위치 칸 서클 「라이다」 · 고른 원천 = 세션 d03-terrain(등록부 한 줄)
- 공용 겹 받기 fetchPlanRaster · fetchPlanZeroOrder 에 terrain 인자(안 넘김 = 옛 그대로 · B05 영향 없음)
- 겹 reterrain = 지형 겹 셋만 비우고 다시 · 높이 = 고른 원천 격자(라이다 범위 밖 = 빈칸)
- 지도는 지형 칸을 넘겨받기만 · 낡은 시험 둘(475 · 624_2)은 글 대조 줄만 새 꼴로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:15:14 +09:00
eomsangdonandClaude Opus 5.5 74d8ae356a knowledge(요구): 내역 10 원문 기록 — 원가계산서 현행 비목 다섯 셈 진행
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:59:36 +09:00
eomsangdonandClaude Opus 5.5 a5179e4dfe fix(공용 지형): 전국 0차곡 이웃 도엽 받기 실패 36 가름 · 면 도곽 · zip 없는 도엽 · 어긋난 도곽 = 빈 지형 (PLAN 628-3)
- 도곽 겹 N3A_H0010000(면) 도 읽음 — 섬 · 해안 작은 zip 은 선 도곽 · 도로 면 없이 면 도곽뿐
- Ver2.0 이 NGI · NDA 뿐인 도엽 = LookupError(zip 없음) — 옛 길은 NGI 글을 zip 으로 받다 로그인 실패로 오판
- 공용 지형 받기: ValueError(도곽이 번호 자리와 어긋남 = 제주 · 울릉 번호 체계) 도 빈 지형 · 로그인 · 망 실패는 그대로 실패
- 시험 test_628_3_sheet_fetch_fallback 새로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:58:41 +09:00
eomsangdon 216e7c565b Merge remote-tracking branch 'origin/sub_laptop_3' into sub_laptop_2 2026-10-31 20:48:27 +09:00
eomsangdonandClaude Opus 5.5 c6a1efad0c refactor(공용 지도): 겹 받기 · 범례 줄 · 급경사 범위 · 위성 밝기 · 겹 자료 도움을 A00_Common/map 으로 — D03 은 다시 내놓기 · 동작 그대로 (PLAN 624-3 · 요구 530)
- D03_Api 겹 받기 → map_layer_api.ts(D03_Api 가 다시 내놓음)
- UI_SlopeBands · logic SlopeBands · UI_SatLight → map_slope_bands · logic/map_slope_bands · map_sat_light
- 범례 줄 legendRows → map_legend.ts(받은 자료 꼴 LegendData · D03 LayerLegend 가 다시 내놓음)
- glImage · glTagged · shadeImage · loadImage · WATER_KEYS · FOREST_ROAD_KINDS · hectare → map_layer_data.ts
- 시험 경로 고침 — test_603(TS 자리) · test_606_1(밝기 파일) · test_584_3(범례 글 = 공용 파일도 읽음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:47:50 +09:00
eomsangdonandClaude Opus 5.5 7cd09426df chore(계획): PLAN 627 이관 — 깨진 시험 6 모두 낡은 시험 · 가짜값만 고침
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 20:37:43 +09:00
eomsangdonandClaude Opus 5.5 db45b05420 test(627-1): 낡은 시험 6 고침 — test_283 손 고침 저장 · test_347_2 배수유역 실시간 가짜값 현행화
- test_347_2 다섯(세대 둘 · 저장 노선 답 · 초안 답 · 잠금) — 59ae735ed(609-1 공용 하강장)가 _prepare 에 좌표계 칸 crs 를 더함 · 시험 가짜 _prepare 에 crs 없어 KeyError · 가짜에 crs None 더함(코드 의도대로)
- test_283 한 본문 손 고침 — b92fa06f3(498-2)가 _save_alignment 에 계획고 본문 꼴 검사(ProfileAlignmentSaveRequest 아니면 약속으로 기다림)를 더함 · 시험 가짜 model_copy 가 SimpleNamespace 를 내어 await 실패 · 가짜가 ProfileAlignmentSaveRequest.model_construct 를 내게 고침
- 코드 흠 없음 · B05 · B06 · B04 코드 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:36:38 +09:00
eomsangdon 82c19c96f5 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-31 20:33:55 +09:00
eomsangdonandClaude Opus 5.5 de8f2fee4a feat(D03): 예정노선 라이다 — LAS 올리기 · B04 엔진 지형 · terrain=lidar (PLAN 624-4 · 요구 530 · 531)
- POST /plan/lidar(LAS · LAZ + .prj · 예정노선만 · 다시 올리면 갈음) → 뒤에서 지형(진행 = /plan/progress lidar) · GET /plan/lidar
- 자리 = D 의 B04_PreProcess/lidar/(안은 B 와 같은 꼴) · LAS 좌표계 다르면 x · y 옮겨 적음 · DTM = surface_models 행(높이 격자)
- terrain=lidar 급경사 · 음영 · 0차곡 = 올린 DTM · 범위 밖 = 빈칸(수치지형도로 안 메움 · 결정 A⑥)
- B04 run_surface_analysis 에 maps 인자 덧(기본 True = B 그대로 · False = 지도 · GIS 받기 · 도엽 서피스 건너뜀)
- 시험 test_624_4_plan_lidar 4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:33:47 +09:00
eomsangdon a34c3c04bd Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_3 2026-10-31 20:32:02 +09:00
eomsangdon c9f5eb70ca Merge remote-tracking branches 'origin/sub_laptop_2', 'origin/sub_laptop_3' and 'origin/sub_laptop_8' into main_laptop_1 2026-10-31 20:30:56 +09:00
eomsangdonandClaude Opus 5.5 85ab474320 feat(D03 긋기): 길이·기울기 고정 부채꼴 · 정방향 채움 · +/− 노드 색 · Shift 사이 기울기 · 완만한 땅 흠 (PLAN 625-2 · 요구 530 · 531)
- 새 셈 logic/D03_PlanAnalysis_Fan.ts(화면 고르기 전용 · 파이썬 짝 없음 · B05 626-3 이 그대로 씀) — 이웃 노드 사이 허용 호 · 정방향 = 앞 구간 쪽(첫 구간 없음 · 갈래 = 붙은 선 · 거의 같으면 마우스 쪽) · 완만한 땅 = 어느 방향이나 · Shift = 호 위 마우스 방위(길이 그대로 · 기울기 실제 지형 · 호 밖 = 호 끝)
- 미리보기 paintFan — 정방향 반투명 채움 · 역방향 테두리 · 노드 + 따뜻한 색 · − 찬 색 · 고리 = 고른 점 부호 색
- 놓인 꺾임점 = 들어온 구간 기울기 부호 색(저장 없이 셈)
- Shift · 완만한 땅으로 찍은 노드 기록 = 길이 고정(저장 꼴 그대로)
- 색 토큰 --draw-map-slope-up · --draw-map-slope-down · test_551_2 토큰 8 → 10
- 시험 test_625_draw(부채꼴 · 정방향 · +/− · 노드 2/4/6/8 · 완만한 땅 · Shift 실제 지형)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:30:53 +09:00
eomsangdonandClaude Opus 5.5 fea37b4ce8 feat(B05): 예정노선 다시 추적 POST /route/follow · 고친 예정노선 expected_route_edit.csv (PLAN 626-2 · 요구 530 D3 · D4 · D8)
- 새 `B05_Profile_Router_RouteFollow.py` — 고친 예정노선(TM · 경위도) → 평면선(노드 · 곡선 · 편집표 · GET /route/plan 칸 이름) · 저장 0
- 같은 함수 — `Route_Polyline_Write.followed_table` 을 체인 초기 폴리라인(`_ensure_planned_initial`)과 함께 씀 · 셈 몫 `polyline_table` 을 쓰기에서 나눔
- [저장] — 노선 초안 `expected`(없음 그대로 · [] 지움 · 점 묶음 씀) → `B05_Profile/route/expected_route_edit.csv` · GET /route/plan 덧칸 `expected_edited`
- [초기화] — 고친 예정노선 지움 · `expected_route.csv` · 초기값 · 추천 저장본 · `/route/replan` · `as_initial` 그대로
- 시험 — 새 `test_626_2_route_follow`(follow = 체인 초기선 · 저장 0 · 경위도 · edit csv 씀 · 지움 · initial_snapshot 지문 · 초기화 지움)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:30:32 +09:00
eomsangdonandClaude Opus 5.5 7fbc4b51a9 fix(공용 지도): 지형 칩 값 las → lidar — 서버 겹 길 terrain= 값(624-1)과 같게 (PLAN 624-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:30:05 +09:00
eomsangdonandClaude Opus 5.5 6c25b864ac feat(공용 지도): 도구줄 표 · 지형 칩 — D03 평면 도구줄을 표로 · 끝에 지형 칩(수치지형도 고름 · 라이다 막힘) (PLAN 624-2 · 요구 530 · 결정 A④ · A⑤ · A⑩)
- map_toolbar = 단추 열쇠 · 할 일 · 보일 페이지(d03 · b05) 표 · mapToolGroups(page, parts) — D03 단추 차례 · 묶음 그대로
- map_terrain_chip = 분할 고르기(수치지형도 · 라이다) · 이름 D03 「분석 지형」 · B05 「설계 지형」 · 없는 원천 막힘 — 바꾸는 일은 onChange(624-1 · 624-4 · 626-1)
- 시험 test_624_2_map_parts 새로 · 553-4 돌리기 자리 확인 = 표

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:28:50 +09:00
eomsangdon 41c950dda9 Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_8 2026-10-31 20:22:20 +09:00
eomsangdon c5b20c5c57 Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_1 2026-10-31 20:21:30 +09:00
eomsangdonandClaude Opus 5.5 c93554d331 fix(B09 불러오기): 코드로만 가리킨 내역 줄도 일위대가에 이음 · 끝수 줄 꼬리표 · 요율 없음 총공사비 숨김 (707-7)
- 영월 꼴(STmate 엑셀 내역 줄 = 호표 코드만) = 일위대가표 같은 코드 호표 번 채움 → 서고 사본 21 · 이음 34
- 이은 줄 차가 비목마다 1 원/단위 안 = 「끝수」(STmate 하위 호표 반올림) · 끝 알림 한 줄 「엑셀 합계와 차 n 원 = 하위 호표 반올림 끝수 m 줄」(브레인 정함 = 끝수 줄도 이음)
- 요율 못 읽은 공사(.estx · .STC) = 총공사비 칸 안 보임
- 시험 test_707_7 11(영월 끝수 · 일위대가 시트 없음 까닭)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:21:15 +09:00
eomsangdon 8c1498a160 Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_2', 'origin/sub_laptop_3' and 'origin/sub_laptop_4' into main_laptop_1 2026-10-31 20:20:31 +09:00
eomsangdon 8eb4dbee13 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_3 2026-10-31 20:19:14 +09:00
eomsangdonandClaude Opus 5.5 e073e575f3 feat(D03): 예정노선 겹 길 임도 프로젝트도 · 지형 원천 인자 · 받은 겹 D03 몫 자리 (PLAN 624-1 · 요구 530 · 531)
- 겹 길(`/plan/layers/*`)이 프로젝트 종류를 안 가림 — 예정노선 = 받은 범위 · 수치지형도 · 임도 = B04 배경 지도 상자 · 설계 지형
- `terrain=sheet|lidar|design` 인자(raster · zero-order) · 응답 `source` · 파생 저장본 열쇠 = 원천 격자 파일 이름
- 라이다 0차곡 = 격자 통째(2 m 솎음 · 파생 저장본) · 수치지형도 = 창고 도엽 선 그대로
- 받은 겹(지적 · 보호구역 · 산림입지 · 단층 · 지질구조선 · 산사태) 자리 = `D03_PlanAnalysis/layers/` — B04 `processed/` 파일 덮음 0 · D 옛 자리 파일은 한 번 복사
- 새 `D03_PlanAnalysis_TerrainSource.py` · 라우터 661 → 658 줄 · 시험 `test_624_1_layers_any_project.py`

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:19:14 +09:00
eomsangdonandClaude Opus 5.5 b96479fd7e refactor(공용 지도): 겹 목록 · GL 겹 꼴 · 겹 줄 · 범례 · 산림 조각 겹을 새 공용 폴더 A00_Common/map 으로 — D03 은 부르기만 · 겉모습 · 동작 그대로 (PLAN 624-2 · 요구 530 · 결정 A⑧)
- map_layer_list = 겹 스물셋 · 처음 값 · 페이지 표(d03 · b05 보일 겹 · 처음 켬 · 취향 열쇠 — A② · A⑨)
- map_layer_specs = GL 겹 꼴 · 차례(D03 UI_MapGl 에서 옮김 · 선 겹 꼴은 LINE_LAYERS 한 벌)
- map_layer_bar = 겹 단추 줄 · 켬끔 · 취향 · 그래픽 카드 단추 / map_legend = 범례 칸 · 조각 겹 범례
- map_forest_layers = D03_UI_ForestLayers 옮김(이름만)
- D03 UI_Layers 683 → 639 줄 · UI_MapGl 423 → 115 줄 · UI_LayerDefs 는 캔버스 도움 + 옛 이름 다시 내놓음
- 시험 길 고침(553-4 · 581-2 · 584-1 · 584-3 · 606-1 · 615 · 622-2) — 옮긴 파일 자리 · 553-4 겹 목록은 622-2 때 낡은 것(국유림 임도 · 경제림 · 생태자연도 빠짐) 같이 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:19:01 +09:00
eomsangdonandClaude Opus 5.5 77a7697768 fix(B04): 새 관 기본값 — 유입 집수정이면 유입 흙막이 칸 안 채움 · 동물이동형 유 · 관경 수 (PLAN 623 · 요구 530 E1)
- `_apply_new_defaults` — 집수정 갈래면 `inlet_revet_*` 기본값 · 높이 안 채움 · `inlet_basin_animal` "true" · 등록부 관경 글 "1000" → 수
- 새 자동 관이 폼을 처음 거쳐도 파일이 안 바뀜(620 증상) · 옛 프로젝트 이주 없음
- 시험 — 새 `test_623_inlet_defaults`(폼 왕복 차 0 · `helper_623_inlet_defaults.cjs`) · 기대값 고침 `test_b05_new_structure_defaults` · `test_211_1`(기슭막이 관으로) · `test_b05_drainage_recommend`(관경 수)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:16:48 +09:00
eomsangdonandClaude Opus 5.5 5d4737210a feat(D03 긋기): 기본값 길이·기울기 고정 · 버튼 자유지정 오른쪽 · Tab 방식 바꿈 · 안 쓰는 칸 숨김 (PLAN 625-1 · 요구 530 · 531)
- 방식 차례 = 자유지정 · 길이·기울기 고정 / 길이 고정 · 기울기 고정 · 「자유」 → 「자유지정」
- 긋기 처음 방식 = 길이·기울기 고정(기억 안 함)
- Tab(긋는 중) = 자유지정 ↔ 길이·기울기 고정 · 등록부 plan.drawKind · 긋기 아닐 때 = 초점 이동 그대로
- 길이 · 기울기 칸 = 방식에 안 쓰면 숨김 · 막힘 = 권한 없음만
- 지도 안내 글 Tab · Shift 줄 · 겹 hover 에 shift 인자(625-2 몫)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:12:48 +09:00
eomsangdonandClaude Opus 5.5 689727fc98 knowledge(요구): 요구 531 원문 기록 — 요구서 530 진행(결정 권함대로)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 20:07:06 +09:00
eomsangdonandClaude Opus 5.5 993cae613e chore(계획): PLAN 622 이관 — 국유림 임도 · 산림입지도 · 경제림 · 생태자연도 · 산사태 지도 겹(줌별 조각)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 20:04:09 +09:00
eomsangdon 55dccd0078 Merge remote-tracking branches 'origin/sub_laptop_3', 'origin/sub_laptop_5' and 'origin/sub_laptop_7' into main_laptop_1 2026-10-31 20:03:20 +09:00
eomsangdonandClaude Opus 5.5 a19425e721 feat(D03 지도 겹): 산사태위험 범례 「산사태 n등급」 이름 붙임 · 검증 창구 layers().zoom(GL 줌) 덧 (PLAN 622-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:01:57 +09:00
eomsangdonandClaude Opus 5.5 76dc4a6143 feat(B09 불러오기): 경쟁사 공사 통째 불러오기 — 창 한 번 · 일위대가 이 공사 서고 사본 (707-7)
- 불러오기 창 = 불러올 것 체크(일위대가 · 단가산출 / 내역 줄 / 원가계산서 요율 / 단가표 · 기본 다 켬 · .estx · .STC 일위대가 · 요율 흐림 + 까닭)
- [불러오기] 한 번 = 서버 차례 — 일위대가(705-3 고정 줄 · 하위 먼저 · 임시 폴더에서 짓고 서고 사본 precombo@project · 층 쓰기 0) → 내역 줄 → 요율 → 단가표 → 셈
- 내역 단가 ≠ 일위대가표 단가 줄 = 잇지 않고 내역 단가 그대로(경쟁사 금액 재현 · 목록)
- 끝 알림 한 장 — 호표 · 내역 줄 · 직접비 · 총공사비 엑셀 = 우리 · 어긋난 줄 · 다음 걸음 · 줄 어긋남 없으면 알림 한 줄
- 내 자료집에 저장(703-2) = 불러온 하위 호표도 지금 층에 같이 씀(한 번만)
- 시험 test_707_7 10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:59:08 +09:00
eomsangdonandClaude Sonnet 5.5 78878e13f4 fix(M02 카드 번호 배지): 카드 머리 번호(SD · SU · SA) 배지 = 시스템 관리자에게만 · 그 밖은 이름 풍선말로만
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 19:48:27 +09:00
eomsangdonandClaude Opus 5.5 4110257460 chore(계획): PLAN 616 · 620 이관 — 0차곡 첫 번 0.4 s · 급경사 칠하기 · B06 관 옵션 칸 까닭 분석
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 19:38:59 +09:00
eomsangdon 4603a08c6c Merge remote-tracking branches 'origin/sub_laptop_2', 'origin/sub_laptop_3' and 'origin/sub_laptop_8' into main_laptop_1 2026-10-31 19:37:29 +09:00
eomsangdonandClaude Opus 5.5 476f239141 knowledge(요구): 저장 속도 재기(sub6) 기록
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:37:09 +09:00
eomsangdon d272474def Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_8 2026-10-31 19:36:03 +09:00
eomsangdonandClaude Opus 5.5 3bd3bcc5a8 perf(B09 [저장] · 708-1): 같은 입력 되풀이 셈 묶음 — 화면 [저장] 3.7 s → 1.7 s · 결과 바이트 같음
- M01 캐시 `steady()` 범위 — 요청 한 번 안 파일 자국 한 번(77 번 × 19 ms → 1 번) · stale · forget 뒤 다시 · 범위 밖 그대로
- 두 번 셈(183-1) 2차 산근 = 1차가 본 단가(있음 · 값)가 같을 때만 1차 풀이 사본 · 다르면 다시 셈
- [저장] 단가 수요 한 번(셈 답에 넘김) · 단계 표시 = 셈과 함께
- B08 `once()` 범위 — B09 원천 읽기 때 토공 채집석 공제가 자재 집계표 답을 다시 씀(두 번 셈 → 한 번)
- 시험: test_b09_save_speed 새로 · test_183 바꿔 끼운 셈 함수 = 새 memo 인자 받음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:34:44 +09:00
eomsangdonandClaude Opus 5.5 31c8279d2c chore(설정): 실수로 올라간 산림 원자료 링크 resources/data_forest 뺌 — git 밖 연결(.gitignore 대상)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:32:54 +09:00
eomsangdon bd8acf8213 auto: 2026-10-31 19:31 (ESD_LAPTOP) 2026-10-31 19:31:46 +09:00
eomsangdonandClaude Opus 5.5 e1537dde96 fix(D03 지도 겹): 생태자연도 · 산사태위험 범례 색 = 조각 그림 색 그대로 — 생태자연도 1 · 2등급 · 별도관리(3등급 칠 없음) · 산사태 범례 전용 토큰 landslide-grade-1~5 (PLAN 622-2)
- 산사태 옛 토큰 landslide-1~5 = 급경사 범위 색이 함께 써서 그대로 둠

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:31:44 +09:00
eomsangdonandClaude Opus 5.5 5d6d7bbb83 perf(D03): 0차곡 무거운 셈 일꾼으로 · 급경사 값 그림 바이너리 · 칠하기 toDataURL (PLAN 616-3 · 616-4)
- 0차곡 길 = 창고 선으로 바로(스레드) · 셈 필요하면 서버 본체 밖 일꾼 · 응답 = json.dumps 바로(큰 선 묶음 jsonable_encoder 건넘)
- 급경사 = PNG 바이트 + 머리 X-Plan-Raster(칸 수 · 범위) · Content-Encoding identity(PNG 다시 gzip 안 함) · 음영기복 · 산사태 = JSON 그대로
- Api fetchPlanRaster 급경사만 바이너리(blob 주소 · slopeValues 가 다 읽고 놓음)
- 칠하기 = canvas.toDataURL(toBlob 한가 작업 기다림 최대 1 s 없앰)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:18:52 +09:00
eomsangdonandClaude Opus 5.5 66f9c68a1e perf(D03): 0차곡 = 창고 격자 · 도엽 선 미리 뽑아 이어 붙임 · 무거운 셈 일꾼 (PLAN 616-1 · 616-2 · 616-3)
- 0차곡 셈 = 사업 DEM 대신 창고 격자(받은 범위 도엽 누적) · 옛 격자 통째 갈래 걷음
- 기본 임계 선 = 도엽마다 창고 `zero_lines_<epsg>_2m.npz`(여유 200 m 에서 뽑아 끝점 도엽만 · 단순화 끝) · 요청 = 이어 붙여 받은 범위로 자름(파생 저장본 안 씀) · 다른 임계 = 누적 이어 붙여 셈 · 저장본
- 창고 누적 열쇠 판 그대로(ACC_VERSION 2 · 전국 누적 다시 셈 없음) · 파생 판 3
- compute=False = 셈 필요하면 None · 서버 본체 밖 일꾼 하나(`heavy_pool` · 띄울 때 빈 __main__ · 서버 끝나면 따라 끝남)
- 미리 계산 단계 zero_lines 덧(zero 뒤 · 기본 단계에 듦)
- 시험 test_616 새로 · test_608 반 칸 어긋난 DEM 통째 셈 확인 걷음(갈래 없어짐) · test_584_1 사업 DEM 통째 0차곡 겹 시험 걷음(창고 길로 바뀜 · 616 시험이 대신)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:18:43 +09:00
eomsangdon 647f850ad8 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-31 19:18:31 +09:00
eomsangdon ad40433bf1 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-31 19:18:27 +09:00
eomsangdon 0d3fc3a471 Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_3 2026-10-31 19:17:12 +09:00
eomsangdonandClaude Opus 5.5 251dfd9a5b feat(D03): 받은 산림 · 환경 자료 줌별 조각 — 벡터(임도 · 산림토양도 · 경제림) · 그림 대리(생태자연도 · 산사태) (PLAN 622-1 · 요구 529)
- 새 common_util_vector_tiles: SHP(cp949 · .prj) → 3857 색인 GeoPackage(data_forest/_index) → z8~15 MVT · 원자료마다 규칙 한 줄(줌 · 단순화 · 작은 면 거름 · 남길 칸) · 창고 storage/_shared/vector_tiles/<name>/v1_<지문> · 615 꼴 잠금 · .part
- 새 common_util_wms_tiles: WMS → 256 px PNG(3857 bbox) · 처음 받고 storage/_shared/wms_tiles 기억 · 빈 · 오류 = 투명 PNG 300 s 기억 · 키 없음 = 404 · 키 안 샘
- 새 D03_PlanAnalysis_Router_Tiles: /plan/vt/{name}/{z}/{x}/{y}.pbf · /plan/wmt/{name}/{z}/{x}/{y}.png · main.py 등록
- 새 tools/precompute_vector_tiles.py: 원자료 이름 · 줌 · 경위도 상자 · 일꾼
- 시험 test_622_vector_tiles.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:16:05 +09:00
eomsangdonandClaude Opus 5.5 d6def61f48 knowledge(요구): 요구서 530 — 예정노선 · B05 평면도 지도 일원화 · 노선 긋기 · 노선추천 미리보기 · 620 고침 + 조사 셋
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 19:14:39 +09:00
eomsangdon c3c79c59ca Merge remote-tracking branches 'origin/sub_laptop_3' and 'origin/sub_laptop_8' into sub_laptop_5 2026-10-31 19:13:12 +09:00
eomsangdon bf2dfed6c1 Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_8 2026-10-31 19:08:38 +09:00
eomsangdonandClaude Opus 5.5 57ff92262b feat(D03 지도 겹): 받은 산림 · 환경 자료 겹 다섯 화면 — 국유림 임도 · 산림입지도 · 경제림 · 생태자연도 · 산사태 = 서버 줌별 조각 고정 소스 · 범례 · 받는 동안 서클 (PLAN 622-2 · 요구 529)
- 공용 부품 tileSource(벡터 pbf · 그림 png) 덧 · 새 파일 UI_ForestLayers(꼴 · 소스 · 범례 · 토양 무리 색)
- 산림입지도 그림 = forest_soil 조각(z10 이상) · 브이월드 받기 · 요약 그대로 · 범례 · 옛 캔버스 색 = 같은 토양 무리 색
- 산사태위험 GL = 조각 그림(받기 없음) · 옛 캔버스 = 받은 범위 한 장 그대로(끌 때 받음)
- 새 겹 셋(국유림 임도 · 경제림 · 생태자연도) 처음 끔 · 옛 캔버스엔 안 보임 · 색 토큰 --draw-map-* 덧 · 아이콘 trees · plant 덧
- UI_Layers 688 → 680 줄(GL 글꼴 미리 받기 = UI_MapGl 로 · 브이월드 토양 GL 꼬리표 뺌)
- 지킴 시험 test_622_2_forest_layers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:08:01 +09:00
eomsangdon 18d5452a25 Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_8 2026-10-31 19:03:14 +09:00
eomsangdonandClaude Opus 5.5 1ba5d49043 knowledge(요구): 620 분석 기록 — B06 관 옵션 칸 지워짐 까닭 커밋 · 고칠 길(데이터 손실 아님)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 19:03:08 +09:00
eomsangdonandClaude Sonnet 5.5 1cab899ecf test(번호 노출): 사전조합 대가 블록 규격 = 층 낱말 · 번호는 spec_raw 시험 (PLAN 704 딸림 ⑤)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 19:02:20 +09:00
eomsangdonandClaude Opus 5.5 64dfd2565c knowledge(요구): 요구 530 원문 기록 — 예정노선 · B05 평면도 지도 일원화 · 노선 긋기 · 노선추천 미리보기 · 조사부터
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 18:55:17 +09:00
eomsangdon 2648c92b70 Merge remote-tracking branches 'origin/sub_laptop_5', 'origin/sub_laptop_7' and 'origin/sub_laptop_8' into main_laptop_1 2026-10-31 18:49:50 +09:00
eomsangdon 3f224788e5 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-31 18:49:45 +09:00
eomsangdonandClaude Opus 5.5 9f3bd27a4c knowledge(요구): 요구 529 원문 기록 — 받은 자료 겹 · 616 진행 동의 · 620 까닭 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 18:49:33 +09:00
eomsangdonandClaude Opus 5.5 ca6db2ac0f knowledge(요구): 요구 528 원문 기록 — 지질 오픈API 후보 물음 · 오늘 자료 확보 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 18:43:46 +09:00
eomsangdonandClaude Sonnet 5.5 51f6eeffb1 fix(B09 통합 점검): 단가연결 낱말 별칭 · 줄 0 개 일위대가 = 빈 줄 · 단위 다름 알림 · 잊은 짝 짝 표 제안 제외 (통합 점검 흠 ① ③ ④ + 잊기)
- 기준 목록 별칭에 「터파기」→「파기」 낱말(측구파기 → 측구터파기) · 목록 모양 SHAPE 올림
- 줄 0 개 일위대가 = 빈 줄 하나 · 빈 줄 있는 대가를 이은 내역 줄 = 단가 모름(금액 0 아님) · py · ts 짝 + 거울 시험
- 단가연결 때 줄 단위 ≠ 일위대가 단위면 「단위 다름」 알림(연결은 됨)
- 잊은 짝은 짝 표 제안에서도 안 나옴(다시 이으면 풀림)
- 낡은 시험 여섯: 707-4 잇기 메뉴 묶음(UI_Link) 빠진 소스 목록 보충

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 18:43:32 +09:00
eomsangdonandClaude Sonnet 5.5 63fd282771 fix(M02 올리기 요청): 회사 관리자는 어느 층을 열어도 「올리기 요청 n」 띠가 보임 (PLAN 706-1 딸림 ⑦)
- 띠 조건에서 「회사 층일 때만」 뺌 — 회사를 고칠 수 있는 사람이면 개인 · 마스터 층에서도 보임 · 누르면 요청 목록

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 18:34:48 +09:00
eomsangdonandClaude Sonnet 5.5 5dc014e9ee fix(M02 구조물 조합): [구조물 조합으로] 만든 항목이 열린 표(행렬)에 바로 보임 — [새로고침] 없이 (PLAN 704-4 딸림 ⑥)
- 만들기 알림이 오면 나무 · 옆 목록에 더해 구조물 조합 표가 열려 있으면 다시 읽음(저장 안 한 고침은 먼저 물음)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 18:34:47 +09:00
eomsangdonandClaude Sonnet 5.5 8c65a323cc fix(번호 노출): 복사 창 규격 · 재료 창고 규격 · B09 줄 비고 · M02 격자 머리 · 비고 = 사람 말(층 낱말 · 이름) · 번호는 풍선만 (PLAN 704 딸림 ⑤)
- 사전조합 대가 블록 규격 = 「내 자료집 · 회사 · 마스터」 · 키는 spec_raw(풍선) · 복사해 새 일위대가 창 규격은 빈 칸
- B09 설계내역서 구조물 줄 비고 = 「층 낱말 · 조합 이름」(옛 「personal|1/3|SP3-000015」)
- M02 일위대가 격자 머리 = 번호꼴 일위대가번이면 이름 · 「일위대가」 · 참조 줄 비고 = 층 낱말 · 참조 창 제목 · 이미 담음 알림에서 키 뺌
- 내 자료집 저장 알림 · 같은 이름 물음에서 키 뺌
- 시험: test_704_3 격자 · test_701_1 비고 고침

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 18:30:51 +09:00
eomsangdonandClaude Sonnet 5.5 3c2c82c78e fix(M02 기준 목록·계산서): 끝수 = B09 산근 규칙(줄마다 버림 · 비목마다 버림)으로 맞춤 — 계산서 5,654 대 B09 5,651 어긋남 (PLAN 704-2 딸림)
- 새 `M02_Logic_Settle` — 엔진 구조 그대로 줄 ROUNDDOWN 1 · 비목 ROUNDDOWN 0 · 작업량 Q ROUND 2 · 하위 로직 같은 규칙
- 기준 목록 금액 · 계산서 줄 · 소계 · 계가 같은 값 · 목록 저장본 꼴 번호 올림 · 머리 글에 지식DB 단수처리 근거
- 시험: 거울 test_704_settle(B09 _money · block_totals) · 옛 목록 시험은 끝수 차 3 원 안으로 고침

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 18:19:09 +09:00
eomsangdon 01334a5730 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_6 2026-10-31 18:16:53 +09:00
eomsangdonandClaude Opus 5.5 8adf98f401 knowledge(요구): 요구 527 원문 기록 — 지질구조선 출처 · 마지막 남은 자료 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 18:16:36 +09:00
eomsangdonandClaude Opus 5.5 fd76c5b76e knowledge(요구): 내역 9 원문 기록 — 현행 비목 물음 · 저장 속도 · 이어서 진행
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 18:15:30 +09:00
eomsangdonandClaude Opus 5.5 141de5bd69 knowledge(요구): 요구 526 원문 기록 — 생태자연도 오픈API 승인(키 값 빼고)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 18:12:03 +09:00
eomsangdon 8626b1a922 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_8' into sub_laptop_5 2026-10-31 18:11:07 +09:00
eomsangdonandClaude Opus 5.5 ab5e5c8277 knowledge(요구): 요구 525 원문 기록 — 임상도 제외 · 다음 보호 · 규제 구역 · 생태자연도 · 지질도
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 18:09:40 +09:00
eomsangdon edeb06698b Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_8 2026-10-31 18:09:11 +09:00
eomsangdonandClaude Opus 5.5 36200c2756 knowledge(요구): 통합 길 점검(sub6) 기록 — 대표 일 최종 누름 · 이음매 흠
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 18:09:10 +09:00
eomsangdonandClaude Opus 5.5 481363e792 knowledge(요구): 요구 524 원문 기록 — 국가임도만 = 맞음 · 지방 · 민간임도는 사용자 입력 · 임도망도 전체 받음 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 18:03:10 +09:00
eomsangdonandClaude Opus 5.5 e0b90876ce knowledge(요구): 요구 523 원문 기록 — 받은 셋 = 오픈API · 임도망도 경북 47.zip 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 17:51:31 +09:00
eomsangdon 1d2425ee53 Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_8 2026-10-31 17:46:53 +09:00
eomsangdonandClaude Sonnet 5.5 74f3fb8009 feat(B09 설계내역서): 불러오기 끝 = 지난번 이음 바로 잇고 남은 제안만 창에(남은 0 = 창 없음) · 잊은 짝 곁 느슨한 기억 안 좁힘 (PLAN 707-4 덧)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 17:46:00 +09:00
eomsangdonandClaude Opus 5.5 b3f3aed0a5 fix(M02 통합 점검 · 704-4 × 706-1): 회사에 올리기 승인 · 회사로 복사 때 규격 열 수량이 버려지던 흠
- 흠: 개인 층 하위 호표(사전조합)를 담은 구조물 조합을 회사로 올리면 참조가 회사 새 키로 바뀌는데
  규격 열 수량 열쇠(`참조|personal|…`)는 그대로 → 그 줄 열 값이 버려져 기본 수량으로 셈
  (실측: 거창 제5 · 6호표 묶음 2.5x2.0 열 1,270,715.6 → 회사 사본 1,259,136.2 · 차 11,579.4)
- 고침: 참조 따라 복사(carry)할 때 규격 수량 열쇠도 새 참조로 바꿈 · 따라 복사되는 하위 문서도 같게
- 고친 파일: M02_MasterTemplete/M02_Labor_Ref.py · resources/tester/test_m02_labor_ref.py
- 확인: 시험 test_m02_labor_ref · test_706_1 27 · test_704_4_spec 10 통과 · 브레인검증회사 다시 요청 · 승인 → 회사 사본 2.5 열 1,270,715.6 = 원본

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:39:41 +09:00
eomsangdon 3747c3388a Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_8 2026-10-31 17:38:06 +09:00
eomsangdonandClaude Opus 5.5 0e8270518b knowledge(요구): 요구 522 원문 기록 — 화면 확인은 나중에 한 번에 · 다음 찾을 자료 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 17:35:01 +09:00
eomsangdonandClaude Opus 5.5 f37a521136 feat(B09 불러오기): .STC(STmate 원본) 바로 읽기 — 내역 BOQ11 · 단가 COSTN · 호표 목록 · 불러오기 길 · 창 *.STC (PLAN 707-6)
- B09_Estimation_Stc — DBF 필드 이름으로 읽음 · 묶음 T · 소계 E · 제외 C · 호표 번 = 같은 글자 코드 정렬 차례
- 다공구(BOQ12) · 변경 차수 = 당초만 읽고 알림 · BDQTY · COSTN.SMT · 요율 = UNKNOWN(같은 공사 엑셀로)
- Router_Import 이름 끝 갈림(.estx · .stc) · 미리 보기 알림 · UI_Import 파일 고르기 *.stc · 창 알림 줄
- 시험 test_707_6_stc 16 — 실무 STC 일곱 직접비 = PRJLST · 기번3 · 영월 · 봉화 엑셀과 줄마다 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:29:24 +09:00
eomsangdonandClaude Sonnet 5.5 0959f28717 feat(B09 설계내역서): 이 짝 잊기 = 잊음 표시를 기억에 남김 — 느슨한 기억(품명 · 단위 같음)으로도 다시 제안 안 됨 · 다시 이으면 풀림 (PLAN 707-4 덧)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 17:29:04 +09:00
eomsangdonandClaude Opus 5.5 1d71b66dea chore(설정): 생활안전지도 키 .env 에 넣음 · 요구 520 · 521 원문 기록(키 값 빼고) — 오류신고 취소
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 17:28:40 +09:00
eomsangdonandClaude Opus 5.5 a5b5dc1b50 knowledge(요구): 요구 517 조사 기록 — 전국 산림 주제도 다른 창구 · 상용 조건 · 공식 요청 길
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 17:27:09 +09:00
eomsangdon ee6f05bd00 Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_5 2026-10-31 17:25:36 +09:00
eomsangdon 7d03b6f369 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_5 2026-10-31 17:25:31 +09:00
eomsangdon c99da6237f Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_5 2026-10-31 17:25:29 +09:00
eomsangdonandClaude Opus 5.5 9a6fd80e75 knowledge(요구): 요구 519 원문 기록 — 산사태위험지도 WMS 포털 활용신청 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 17:24:35 +09:00
eomsangdonandClaude Opus 5.5 39684cc404 Merge origin/sub_laptop_5 into sub_laptop_8 — PLAN 707-4 공종 잇기 화면 · 불러오기 창 끝 겹침 양쪽 살림(단가표 같이 · 잇기 창 이어 뜸)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:24:21 +09:00
eomsangdonandClaude Opus 5.5 5655c628b9 feat(B09 설계내역서): 내역 줄 ↔ 우리 공종 잇기 창 — 모두 제안대로 · 줄마다 잇기 · 잇기 풀기 · 이 짝 잊기 (PLAN 707-4)
- 새 화면 B09_Estimation_UI_Link — 제안(지난번 이음 · 짝 표 · 이름 같음 꼬리표 · 후보 고름) · 수량 갈래(불러올 때 고른 것 · 줄에서 바꿈) · 묶음 개수 칸 · 제안 없는 줄 = 단가연결 고르기 창 재사용(우리 토공 · 운반 · 구조물 줄만) · 이은 줄 풀기 · 잊기 · 남은 안 이은 줄 수
- 고리 둘 — 불러오기 끝 = 잇기 창 이어 뜸 · 설계내역서 우클릭 = 잇기 · 풀기 · 잊기
- 「고름」 줄 기본 = 앞 줄이 안 쓴 후보 · 모두 제안대로 = 같은 우리 줄 둘째부터 남김
- apply 알림에 안 이은 불러온 항목 수

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:21:01 +09:00
eomsangdonandClaude Opus 5.5 ebd33de9c9 fix(B09 통합 점검 · 703-2 × 703-3): 사전조합에서 복사한 일위대가 — [업데이트] 뒤 금액 0 · [저장] 때 원본 사본 지움 막음
- 흠: 사전조합 연결 줄을 「복사해 새 일위대가」 → [바꿈] 하면 복사본 원본 열쇠가 `C:참조…` 꼴인데 쓰는 사본 셈(used_folders)이 `L:` 만 봄
  → [업데이트] 수요에서 원본 사본 노임 줄이 빠져 책에서 지워짐(금액 40,490 → 0 · 빈 줄 3) · [저장] 정리가 원본 사본 폴더를 지울 수 있음
- 고침: 복사본의 복사본(`K:`)은 맨 처음 원본까지 따라감 · `C:참조` = 그 참조를 담은 사본 폴더도 지킴(프로젝트 자리 줄 때)
- 고친 파일: B09_Estimation/B09_Estimation_Bill_Library.py · B09_Estimation/B09_Estimation_Router.py · resources/tester/test_703_b09_daega_copy.py
- 확인: 시험 test_703 · 703_1 · 703_3 · 204_2 · 251_1 29 통과 · 검증_sub6 [업데이트] 뒤 측구파기(토사) 40,490 그대로 · [저장] 뒤 서버 161,960 = 화면 · 사본 폴더 남음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:20:45 +09:00
eomsangdonandClaude Opus 5.5 02dc4f5842 knowledge(요구): 요구 518 원문 기록 — 경제림육성단지 파일 저장 · 오픈API 승인 확인(키 값 빼고)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 17:20:08 +09:00
eomsangdon 185ab410e1 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_8' into sub_laptop_7 2026-10-31 17:18:30 +09:00
eomsangdon ab5d1c96b5 Merge remote-tracking branch 'origin/dev' into sub_laptop_7 2026-10-31 17:18:26 +09:00
eomsangdon 66bec3205f Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_8 2026-10-31 17:16:02 +09:00
eomsangdonandClaude Opus 5.5 a8cf79cee0 fix(공용 표 높이): 창 끝까지 셈(spaceBelow)이 열린 창 가림막 · 알림(fixed · absolute)을 표 아래 자리로 재던 흠 (PLAN 707-5 딸림)
- body 끝 가림막(창 높이)이 뒤 형제로 잡혀 창이 열린 채 다시 그리면 표가 최소 120px 로 짜부
- 흐름 밖(position fixed · absolute) 형제 = 자리 안 씀
- 부품 모음 견본(화면 끝 표 · 견본 창): 전 794 → 120px · 뒤 794 → 794px(밝음 · 어두움)
- 시험 test_202_1 가림막 줄 덧붙임

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:14:51 +09:00
eomsangdonandClaude Opus 5.5 1bb3b43064 test(낡은 시험): 합친 판 실패 넷 — 코드 흠 아님 · 모두 뒤 일감이 바꾼 자리 (PLAN 707-5 딸림)
- test_224_1 · helper_280_1: 고르기 창 몸 = 위 표 · 규격 표(704-4 specHost) · 열 — 둘째 자리 → 셋째 자리
- test_280_1 · test_b05_structures_form_summary 도우미: 부품 상자 글꼴 import(ui_template_icons.css)를 .ts 로 찾던 흠 — .css = 빈 모듈
- test_m02_precombo_scope: precombo 배선에 706-1 `more` 덧붙음 — 한 줄 글 대조 → scope · bar 줄 대조
- test_structures_summary_snapshot: robocopy 가 복제 도구 본체(common_util_project_copy)로 옮겨 감 — 껍데기 대신 본체 읽음
- test_202_1 · test_704_4: 합친 판에서 통과 — 고칠 것 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:07:57 +09:00
eomsangdon 4cc39a1e56 auto: 2026-10-31 17:07 (ESD_LAPTOP) 2026-10-31 17:07:54 +09:00
eomsangdonandClaude Opus 5.5 f1e59f0e5b knowledge(요구): 요구 516 원문 기록 — 산림 자료 git 에 올림 · 견본 물음 · 공공데이터포털 받기 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 17:01:51 +09:00
eomsangdonandClaude Opus 5.5 56ebe76e7f feat(B09 불러오기): .estx 고리 — 내역 줄 · 단가표 같은 길 · 요율 = 엑셀 안내 · 호표 목록 = 이음 후보 수 (PLAN 707-5)
- 길: 이름 끝 .estx = `B09_Estimation_Estx` read_bill · read_prices(같은 그릇) · 요율 = 400 「같은 공사 엑셀에서」
- 미리 보기 `호표` = 대가 · 산근 목록 수 + 내역 줄이 코드로 가리킨 수(구성 줄 귀속은 안 함)
- 창: 파일 고르기 *.estx · [단가표도 같이](받은 patch 를 캐시에 함께) · 지난 파일 요율 실패 = 까닭 알림 뒤 다시 고름
- 시험 test_707_5 8(길 갈림 · 호표 수 · 깨진 파일 400)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:59:53 +09:00
eomsangdon e22fd23c6c Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_6 2026-10-31 16:58:29 +09:00
eomsangdonandClaude Opus 5.5 3eda7f1d26 chore(계획): PLAN 619 이관 — 공공데이터포털 키 .env · 산림 주제도 공개 파일 받기 (요구 514 · 515)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 16:58:24 +09:00
eomsangdonandClaude Opus 5.5 1a4c1b5178 feat(B09 설계내역서): 내역 잇기 서버 길 끼움 — 잇기 풀기 · 이 짝 잊기 · 줄마다 수량 갈래 · 잇기 판 (PLAN 707-4)
- Router 에 Router_Link 끼움
- 이은 자동 줄 was = 불러온 줄 · price 안 줄 · 자동 줄 덮개 · 같은 부모 앞 뒤 줄 키 → 풀기 = 제자리
- link/undo · link/forget 길 · suggest 답에 남은 줄 · 빈 자리 · 이은 줄(구조물 묶음에 딸린 줄 뺌)
- 시험 test_707_4 4 더함(풀기 차례 바꿔도 제자리 · 줄마다 갈래 · 판 · 일반 계정 회사 기억 못 지움)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:58:12 +09:00
eomsangdon 2d05b431d2 Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_8' into main_laptop_1 2026-10-31 16:57:36 +09:00
eomsangdonandClaude Opus 5.5 bf0f01d760 chore(secrets): 공공데이터포털 키 .env DATA_GO_KR_API_KEY 로 옮김 · 산림 주제도 폴더 추적 제외
- read_g2b_key 가 .env 의 DATA_GO_KR_API_KEY 읽음(없으면 멈춤)
- .secrets.local.md 키 값 줄 → 옮김 안내 한 줄
- .gitignore 에 resources/data_forest/ (산림청 공개 파일 원본)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:54:39 +09:00
eomsangdon 31cb4ba3af Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_8 2026-10-31 16:53:45 +09:00
eomsangdonandClaude Opus 5.5 2e0552c9d5 fix(704-4 구조물 조합으로): 만든 알림 이름 = 적은 이름(폴더 이름 겹침 없앰)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:51:56 +09:00
eomsangdon f1323eff60 Merge remote-tracking branches 'origin/sub_laptop_6' and 'origin/sub_laptop_8' into sub_laptop_5 2026-10-31 16:48:55 +09:00
eomsangdon 0e106f36bf Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-10-31 16:48:51 +09:00
eomsangdon 8cbd069cc1 Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_8 2026-10-31 16:46:48 +09:00
eomsangdonandClaude Opus 5.5 77c6f9105c feat(B09 설계내역서): 내역 줄 ↔ 우리 공종 잇기 서버 몫 — 토공 이름 짝 표 · 맞춤 기억 · 구조물 묶음 통째 잇기 (PLAN 707-4)
- B09_Estimation_Bill_Link: 짝 표(우리 토공 17 줄 · EST · STmate 이름 24 짝 · 암 갈래 = 후보 둘) · 제안(지난번 이음 · 짝 표 · 하나 아님 = 고름) · 잇기(707-1 붙임 꼴 · take = 엑셀 수량 덮개 / price = B08 · 불러온 줄 지움 = 두 번 셈 0) · 묶음 ↔ 구조물 줄(take = 안 줄이 그 아래 / price = 1 단위 고정 단가)
- B09_Estimation_Router_Link: suggest · apply 길(끼워 넣기 = 화면 고리 일감) · 맞춤 기억 = 층 실무자료/내역맞춤.json(회사 관리자 = 회사 · 아니면 개인 · 읽기 = 회사 + 개인)
- M02_Hopyo_Store: memory · remember 에 기억 파일 이름 칸(기본 = 맞춤)
- 시험 test_707_4 8(거창 엑셀 기억 → 장수 · 진안 .estx 저절로 11 · 두 번 셈 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:45:36 +09:00
eomsangdon ae90737cf7 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_2 2026-10-31 16:42:32 +09:00
eomsangdonandClaude Opus 5.5 86f40dedbd fix(705-2 붙여넣기 · 다시 잇기): 화면 검증에서 나온 흠 — 첫 줄 머리 짐작 · 열 짝 한 칸 밀림 · 저절로 고름 · 칸 폭 · B09 최신 캐시
- 첫 줄이 수량 없음이어도 금액 없으면(레미콘 별도계산) 줄 — 머리는 금액 있을 때만
- 기억한 열 짝을 다른 꼴(엑셀꼴 호표 칸 · STmate 꼴)에 붙이면 서버가 한 칸 밀어 맞춤
- 다시 잇기 저절로 고름 = 후보 하나 · 붙인 단가 있음만(별도계상 0 원 · 후보 여럿은 사람이 고름) · 빈 칸 글 없앰 · 고름 칸 앞으로 · 칸 폭 1100 안
- B09 알림에서 연 다시 잇기가 붙이기 전 캐시를 읽던 흠 — 붙인 뒤 캐시를 들고 감

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:42:28 +09:00
eomsangdonandClaude Opus 5.5 76c2b1cf49 knowledge(요구): 요구 515 원문 기록 — 공공데이터포털 키 .env 로 · 산림 자료 resources 에 받기 · CPU 표시 차 까닭
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 16:41:55 +09:00
eomsangdonandClaude Opus 5.5 6b6b1575eb Merge origin/sub_laptop_7 into sub_laptop_8 — PLAN 707-2 · 707-3 요율 · 단가표 불러오기 · M02 낱말 · B09 Store 머리 설명 겹침 양쪽 살림(705-2 @add · 707-3 노임 · 기계 덮개)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:39:23 +09:00
eomsangdonandClaude Opus 5.5 f64111ec53 merge(707): sub_laptop_8(sub5 707-1 내역 줄 불러오기) 합침 — 불러오기 길 · 화면 한 파일 · file_key 하나 (PLAN 707-1 · 707-2 · 707-3)
- Router_Import = import/bill · bill/apply(sub5) + import/rates · import/prices · 올린 파일 = 프로젝트 import/{표지} 한 자리
- UI_Import = [엑셀 내역 불러오기] + [엑셀에서 요율 · 단가표 불러오기] · 마지막 표지 함께 씀
- test_145_3 · test_166_2 TS 거울 = 글 stdin(Calc_Base · Calc_Bill 길어져 node -e 32 K 넘김)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:30:43 +09:00
eomsangdonandClaude Opus 5.5 6aa06d618b feat(M02 고르기 창 낱말): ★ 12 → 1 — 옵션 고치기 · 이 구조물에서 고치기 · 내 자료집에 복사 · 일위대가 금액 · 값 확인 전 · 면적산출 연결 칸 · 세부 구분 · 실무 엑셀 일위대가 · 옵션 머리 · 카드 · 목록 번호 숨김 (PLAN 707 ③)
- 남김 1 = 분류1~5(구조물집계표 층 이름 = 마스터 템플릿 자료 · 코드 밖)
- ui_template 네 파일 글만(한시 브레인 승인) · 키 · 동작 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:24:12 +09:00
eomsangdonandClaude Opus 5.5 550c0adcce feat(B09 설계내역서): .estx(EST 원본) 바로 읽기 모듈 — 내역 · 호표 목록 · 노임 · 자재 · 기계 평문 표 (PLAN 707-5)
- B09_Estimation_Estx: ZIP 안 DBISAM 고정 레코드 표를 메모리에서 읽음 · 엑셀 읽기와 같은 꼴(707-1 내역 · 707-3 단가표)
- 구성 줄 → 호표 귀속 · 제N호표 번호 · 원가계산서 행 경계는 안 읽음(UNKNOWN · 데스크탑 REA 필요)
- 시험 test_707_5_estx: 거창 = 엑셀과 줄마다 같음 · 직접비 384,916,227 · 장수 · 진안 줄 수 · 합 기록

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:24:02 +09:00
eomsangdon 3d59685e93 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_5 2026-10-31 16:18:26 +09:00
eomsangdon 5db7eba0e5 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_5 2026-10-31 16:18:24 +09:00
eomsangdonandClaude Opus 5.5 f9ffd5fc77 feat(M02 사전조합 → 구조물 조합 한 단계): 실무자료 확인 창 [구조물 조합으로] · 규격 묶음 제안 (PLAN 704-4 뒷부분)
- 확인 창 몸 그대로(체크 = 품셈 줄 · 나머지 = 고정 줄)로 구조물 조합 항목(SU)을 바로 만듦 — 사전조합 SC 문서 안 씀 · 옛 [사전조합 생성] 길 그대로
- 규격 묶음 제안 — 같은 이름 · 단위 · 줄 구성 · 규격만 다른 호표 → 한 항목의 규격 열(H=1.5 → 이름 H · 열 1.5) · 열마다 같은 몸으로 만든 줄 수량 · 기본 열 = 연 호표
- 서버 M02_Hopyo_Item · /hopyo/{키}/{번}/siblings · /hopyo-item(이 층 쓰기 · 「실무자료」 폴더) · Builder.build(save=False)
- 규격 열 줄 id 가 고정 줄을 못 알아보던 흠 — line_key = fixed_id(TS lineId 와 같음)
- 만든 뒤 나무 · 옆 목록 다시
- 시험: 울진 대흥 돌기슭막이 H 1.5 / 2.0 / 2.5 = 한 항목 열 셋 · 열마다 = 그 호표 엑셀 합계 · 줄 구성 다른 호표 400 · 남의 층 403 · 사전조합 0 · 마스터 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:14:59 +09:00
eomsangdonandClaude Opus 5.5 8956a6c902 test(707-1): 내역 줄 이음 시험 — 같은 책 일위대가 이음 단가 · 어긋남 알림 · 이음 찾기(같은 파일 이름 본뜬만)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:14:36 +09:00
eomsangdonandClaude Opus 5.5 c33f6837f2 fix(B09 설계내역서): 엑셀 내역 불러오기 화면 검증 흠 — 단가만 빈칸 · 운반 줄 오붙음 · 차례 · 표 높이 (PLAN 707-1)
- 단가만 갈래 = 붙인 자동 줄에 B08 수량이 없으면 엑셀 수량(빈칸 금액 막음)
- 운반 자동 줄(「모래」 · 「시멘트」 = 자재대 줄 이름) = 붙이지 않음 · 지우기만
- 불러온 공종 = 관급자재대 앞(엑셀 차례 · 표 끝에 묻히지 않음)
- 결과 창 = 표를 다시 그린 뒤 따로 엶(열린 창 가림막을 표 아래 자리로 재어 표가 한 줄로 줄던 것) · 어긋난 줄 표 칸 폭 줄임
- 못 읽은 파일(빈 · DRM · 내역 시트 없음) = 프로젝트 import 폴더에 안 둠

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:13:34 +09:00
eomsangdonandClaude Opus 5.5 a5f87812e6 feat(B09 불러오기 화면): 총괄 · 원가계산서 [엑셀에서 요율 불러오기] · 노무 · 중기 · 자재단가대비표 [엑셀에서 단가표 불러오기] · 확인 창 · [적용] = 캐시 (PLAN 707-2 · 707-3)
- 확인 창 = 짝 표 · 우리 식 대조(같음 · 식 다름 · 안 고른 쪽) · 짝 없음 n · 마지막 파일 열쇠 다시 씀 · [다른 파일]
- 단가표 이음 수요 = 화면 단가 목록(GET)과 같은 사본 전부
- test_243_1 B09 표 목록 — 702-1 펼침 표(받기 전부터 실패) · 707 확인 창(보기 전용) 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:06:42 +09:00
eomsangdonandClaude Opus 5.5 175857de81 feat(B09 펼친 일위대가 붙여넣기): Ctrl+V = 지금 호표 끝 고정 줄 · 이 공사 단가로 다시 잇기 (PLAN 705-2)
- 펼친 일위대가 표를 누르고 Ctrl+V → 서버 /estimation/daega/paste(붙인 글 · M02 와 같은 해석 · 열 짝 물음 · 기억) → 덮개 @add 줄(캐시 · [저장] 때 서버가 다시 셈)
- 셈 짝(파이썬 · TS) — @add 줄을 그 cover 블록 끝에 더함(복사본 앞 · 뒤 · 같은 cid 한 번) · 짝 잃음에서 뺌 · 저장 정리 clean_added
- 붙인 줄 이음 키 = 단가 책 수요(added_keys) · 답에 그 캐시로 셈한 원가 자료
- 알림 누름 = 다시 잇기 창(후보 = M02 이음 짝 · 단가 = 이 공사 단가 책 · 바꿈 = 캐시 + 서버 셈)
- 시험: 거울(파이썬 = TS) · 같은 글 두 번 = 「 (2)」 가름 · 정리 · 수요 키

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:04:16 +09:00
eomsangdonandClaude Opus 5.5 32de488401 feat(M02 붙여넣기 화면 · 다시 잇기): 격자 Ctrl+V · 열 짝 한 번 묻고 기억 · 이 공사 단가로 다시 잇기 (PLAN 705-2)
- 일위대가 격자(ui_template_hopyo_grid onPaste) 누르고 Ctrl+V → 서버 /hopyo-paste · 열 짝 = 처음 한 번 묻고 이 브라우저에 기억 · 엑셀 머리 글이면 묻지 않음
- 호표 머리 없음 = 열린 문서에 고정 줄(알림 누름 = 다시 잇기) · 머리 있음 = 호표마다 사전조합 + 결과 창 [다음 — 이 공사 단가로 다시 잇기]
- 다시 잇기 창(EST 신 · 구 단가 비교 꼴) — 붙인 단가 · 최신 판 단가 · 후보(705-3 이음 짝) · 금액 차이 · 줄마다 · 모두 고름 · [바꿈] · [되돌리기]
- 서버 M02_Hopyo_Relink — 후보 표 · 이음 칸만 고쳐 씀(숫자 안 받음) · 전후 1 단위 값 · 되돌림
- 실무자료 [모두 고정으로 들여오기] 결과에도 다음 걸음 단추
- M02 상세가 고정 줄을 읽을 때 떨구던 흠 — normalizeLabor 가 고정 칸 지킴 · 로직 표 · 격자에 고정 줄 이름 · 규격 · 단위 · 「고정」 꼬리표 · 이음 / 손 단가

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:57:39 +09:00
eomsangdonandClaude Opus 5.5 4cd4fdb3a3 feat(B09 불러오기): 원가계산서 요율 · 기초 단가표 읽기 · 이 공사 단가 책 이음 · 올린 파일 열쇠 길 (PLAN 707-2 · 707-3)
- POST estimation/import/rates · import/prices (file | file_key · 저장 안 함 · patch 만)
- 요율 = 행 이름 → 줄 키 · 손입력 칸(안전 기초액 · 이윤 보정 · 부가세 조정 · 조달수수료율) · 엑셀 대조
- 단가표 = 노임 · 자재 · 기계 → labor_cover · machine_cover · materials.applied · 짝 없음 · 짝 여럿

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:53:33 +09:00
eomsangdonandClaude Opus 5.5 4e190bad1a knowledge(요구): 요구 514 원문 기록 — 1:5,000 은 나중 정식 요청 · 되는 범위 받기 · 용량 · 공공데이터포털 키 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 15:51:37 +09:00
eomsangdonandClaude Opus 5.5 02886d5d4d feat(B09 설계내역서): [엑셀 내역 불러오기] 창 — 수량 갈래 · 두 번 셈 막음 · 어긋난 줄 목록 (PLAN 707-1)
- 설계내역서 도구 줄 끝 단추 → 파일 고름 → 창 한 번(넘겨받기 · 단가만 · 이름 같은 자동 줄 붙임 · 남는 자동 줄 지움) → [불러오기] = 화면 캐시(저장 안 함 · [저장]해야 남음)
- 어긋난 줄(엑셀 금액 ≠ 우리 금액) = 같은 창 목록 · 없으면 닫고 알림 · DRM = 「읽을 수 없음」
- 단가만 갈래 = 불러온 수량과 10 % 넘게 다른 수량 칸 붉게 · 풍선 = 불러온 수량
- 시험 243-1(표 만드는 길 목록 = 불러오기 창 표 더함 · 보기 전용) · 200-2(도구 줄 배선 = 불러오기 단추 끝) 고침 — 낡은 글자 대조

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:50:19 +09:00
eomsangdonandClaude Opus 5.5 137b8a3a45 feat(M02 엑셀 붙여넣기): 붙인 글 서버 해석 · 호표마다 사전조합 · 줄만 고정 줄 (PLAN 705-2)
- M02_Hopyo_Paste — 탭 · 줄 글 → 실무자료 꼴(파일 올리기와 같은 꼴) · 머리 글 줄이면 열 짝 저절로 · 없으면 기억한 열 짝 · 둘 다 없으면 짐작 + 물음
- 「제N호표」 엑셀꼴(같은 줄 머리) · STmate 꼴(다음 줄 머리) · 첫 줄 수량 없음 = 머리 · 합계 · 계 · 소계 · 계약단가 · 근거 줄 거름 · 쉼표 · 0 빈칸
- /hopyo-paste — 물음 · 줄(열린 문서 · 있는 줄 가름 피함) · 만듦(실무자료 「붙여넣기 …」 + build_all 그대로)
- build_all 답에 만듦 [{번, 이름, 키}]
- 시험: 실무 xlsx 넷 시트 통째 붙여넣기 = 파일과 호표 · 줄 · 합계 같음 · 만듦 엑셀 합계 = 우리 합계(14 · 23 · 34 · 25)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:46:32 +09:00
eomsangdonandClaude Opus 5.5 c0ea73501f feat(B09 설계내역서): 경쟁사 내역 엑셀 줄 불러오기 서버 — 읽기 · 짜기 · 고정 단가 칸 (PLAN 707-1)
- 읽기(Bill_Excel) — STmate 「설계내역서」 숨은 코드 · 부모 코드 열 · EST 「내역서」 공종 번호 깊이 · 비고 대가 · 산근 · 단산 · 일위 · 제외 묶음(관급 · 제외금액) · 조달수수료 = 율 · DRM = 「읽을 수 없음」 · 열 머리 = M02 실무자료 칸 읽기 재사용
- 짜기(Bill_Import) — 공종 = 새 묶음 · 항목 = 새 줄 + 고정 단가 칸 · 수량 갈래 take · price · 이름 같은 자동 줄 붙임 · 남은 자동 줄 지움 · 같은 파일 다시 = 갈아 끼움 · 어긋난 줄 목록
- 길(Router_Import) — import/bill(file · file_key) 미리 보기 · import/bill/apply(같은 파일 이름 실무자료 사전조합 → 서고 사본 연결 · 층 쓰기 0) · 저장 안 함
- 셈 짝(Calc_Bill py · ts) — 줄 price = 연결이 단가를 못 낼 때 금액 · fixed · src_qty · Store 다듬기
- 확인 = 기번3 710,359,136 · 영월 767,148,700 · 봉화 781,283,415 · 거창 384,916,227 직접비 합 = 엑셀 · 빈 줄 0 · 어긋남 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:43:51 +09:00
eomsangdonandClaude Opus 5.5 0b2330aee6 fix(B09 단가 덮개): TS 이름 겹침(PARTS → COVER_PARTS) · 내역 거울 시험 글을 stdin 으로 (PLAN 707-3)
test_145_3 블록 나누기 시험 — Calc_Base.ts 가 길어져 node -e 명령 줄이 Windows 32 K 를 넘음 · 같은 글을 stdin 으로 넘김(판정 그대로).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:41:17 +09:00
eomsangdonandClaude Opus 5.5 39e0ee6d85 feat(B09 단가 덮개): 노임 · 기계 단가 덮개 labor_cover · machine_cover — 셈 짝 · 저장 다듬기 · 거울 시험 (PLAN 707-3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:39:16 +09:00
eomsangdonandClaude Opus 5.5 df5da11b37 test(243-1): B09 표 만드는 길 시험 — 702-1 펼침 표 더함
702-1 B09_Estimation_UI_Tabs_Bill_Fold.ts 가 펼친 일위대가 구성 표를 제 표로 만듦(editOnDouble: true · 두 번 눌러 고침).
코드 흠 아님 · 시험이 팝업 · Shell 두 곳만 알던 낡은 목록 — 펼침 표를 목록에 더하고 두 번 눌러 고침 확인 한 줄.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:28:05 +09:00
eomsangdon 4f4348c80e Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_5 2026-10-31 15:27:12 +09:00
eomsangdonandClaude Opus 5.5 9b543077ff knowledge(요구): 705-4 경쟁사 결과물 불러오기 범위 조사(sub7) 기록
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:26:10 +09:00
eomsangdon 70ddc55650 Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_8 2026-10-31 15:25:01 +09:00
eomsangdon bbbf963d05 Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_8' into sub_laptop_6 2026-10-31 15:24:20 +09:00
eomsangdon 459002dc4f Merge remote-tracking branch 'origin/dev' into sub_laptop_6 2026-10-31 15:24:17 +09:00
eomsangdonandClaude Opus 5.5 3435922131 feat(B09·B08): 화면 낱말 실무 말로 · 번호 숨김 · ☰ 서랍 내 자료집 길(PLAN 703-4)
- B09 데이터 점검 → 내역 점검 · 공기 옵션 → 공사기간 조건 · 박스보기/호표보기 → 카드 보기/표 보기 ·
  하이퍼링크/팝업 → 탭 이동/곁 창 · 굳힌 단가 → 이 공사 단가 책 · 산식/요율 출처 → 계산식/요율 근거 ·
  빈 줄 → 금액 빈 줄 · 단가연결 칸 램프 → 단가연결 칸 ● · 새 산근 → 새 단가산출(산근) · 연결 없음 → 일위대가 없음
- 단가연결 창 종류 일위대가 사전조합 → 일위대가(자료집) · 일위대가 호표 → 이 공사 일위대가 · 단가산출 호표 →
  이 공사 단가산출 · 대가N/산근 N호표 → 일위대가 N호/단가산출 N호 · 구조물 조합 줄 번호(SS…) 숨김 ·
  넣을 값 창 제목 번호 → 알림 글 끝
- B08 조합 사본 · 조합 연결 · 노랑 램프 → 구조물 조합 · 구조물 조합 고르기 · 노랑 ●(locale_b4 B08_Quantity_* 글만 · 한시 브레인 승인)
- ☰ 서랍 · 진행 판 끝 「내 자료집(일위대가 · 구조물 조합)」 = M02 일위대가조합 개인 층(떠남 물음 뒤)
- 낡은 시험 글 고침(새 낱말): 143-1 · 157-2 · 182 · 203-1 · 204-2 · 304 · 311 · 701-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:18:10 +09:00
eomsangdon 47e1d5687d Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_8 2026-10-31 15:16:26 +09:00
eomsangdonandClaude Opus 5.5 2789afb102 feat(M02 실무자료 빠짐 0): 확인 안 한 줄 · 노임 · % = 고정 줄 · [모두 고정으로 들여오기] · [품셈으로 바꾸기] (PLAN 705-3)
- 만들기 — 체크 안 한 줄 · 노임 · 「우리에 없음」 = 고정 줄(엑셀 숫자 그대로 · 서버가 파일에서) · % 줄 = 비율 고정 줄(밑수 = 다른 줄 합일 때 · 아니면 금액 그대로) · 합계만 = 재료비 · 같은 이름 = 차례 가름 · 하위 호표 = 확인된 사전조합 참조
- 이음 = 이름 · 규격 · 단위 · 단가 다 같은 노임 · 자재만 저절로 · 다른 단가 = 이음 후보 수
- 시트 통째 — /hopyo-build-all(하위 먼저 · 확인된 호표 그대로) · 알림 「호표 · 줄 · 품셈 줄 · 고정 줄 · 빠짐 · 엑셀 합계 = 우리 합계」 · 비교 = 엑셀 끝수 규칙(EST 내림 · STmate 반올림)
- 비율 고정 줄 셈 — M02 settle 짝(파이썬 · TS ratioLines) · 사전조합 셈 · B09 pct 줄
- 확인 창 — 만든 사전조합 품셈 줄 = 첫 체크 · [품셈으로 바꾸기] · [모두 고정으로] · 생성 물음에 전후 금액 · 품 다른 줄 수
- 낡은 시험 고침 — 빈 몸 400 · 빠진 줄(확인 안 함 · %) 단정 = 고정 줄로 바뀐 규칙

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:10:16 +09:00
eomsangdon 69d70a3f68 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_5' into sub_laptop_7 2026-10-31 15:09:33 +09:00
eomsangdonandClaude Opus 5.5 fdb3eb9b77 feat(B09): 오른쪽 재료 창고 · 지난 공사 일위대가 가져오기(PLAN 703-1)
- 설계내역서 표 오른쪽 서랍(공용 side_panels · 접고 펴기 · 좁은 화면 = 아래) — 찾기 하나 + 출처 탭
  이 공사 · 내 것 · 회사 · 품셈(704-1 기준 목록 · 우클릭 계산서 보기) · 지난 공사
- 두 번 누름 · Enter = 고른 내역 줄에 단가연결 · 끌어 놓기 = 놓은 줄 · 연결 = 단가연결 모달과 같은 길(702-3 즉시 금액)
- 품셈 = 넣을 값 창에 계산서 입력 전부(목록 바꿈은 기본과 다른 것만 — 석축헐기 뒷길이 60 이 0 으로 열리던 것)
- 서버 B09_Estimation_Past + GET/POST /estimation/past — 같은 회사 다른 공사 구조물 사본 · 서고 사본 →
  이 공사 서고 사본(폴더 통째 · 가져온곳 · 덮개째 · 단가 = 이 공사 책) · 남의 회사 403
- 표 높이 셈(spaceBelow)이 뒤 형제만 보므로 서랍 = DOM 앞 · CSS order 로 오른쪽
- 시험 test_703_1 6 · 셸 꼴 글 시험 200-2 · 202-1 = 표 칸 + 서랍 꼴로 고침(낡은 글)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:59:58 +09:00
eomsangdonandClaude Opus 5.5 bd4d421668 knowledge(요구): 요구 513 조사 기록 — 산림청 주제도 다섯 받는 길 · 이용 조건
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 14:58:42 +09:00
eomsangdonandClaude Opus 5.5 f0be3cd766 feat(회사에 올리기 화면): 나무 우클릭 요청 · 요청 중 · 회사 층 「올리기 요청 n」 띠 · 목록 미리 보기 · 승인 · 거절 · 결과 띠 (PLAN 706-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:55:46 +09:00
eomsangdonandClaude Opus 5.5 74352ec6b8 feat(회사에 올리기): 요청 장부 · 요청 · 목록 · 승인 · 거절 · 알림 봄 길 + 임시 storage 시험 (PLAN 706-1)
- 회사 층 폴더 올리기요청.json 하나 · DB 안 바꿈
- 승인 = 「회사로 복사…」와 같은 길(구조물 = 붙여넣기 · 사전조합 = 서고 복사 + 폴더 자리) · 원본 개인 것 그대로
- 권한 = M02_Layer_Access 재사용 · 일반 승인 403 · 다른 회사 장부 안 봄 · 같은 것 다시 요청 409

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:52:30 +09:00
eomsangdonandClaude Opus 5.5 4ed0509c7a knowledge(요구): 요구 513 원문 기록 — 산림청 map.forest.go.kr 주제도 API 확인만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 14:47:08 +09:00
eomsangdon f91ad85826 Merge remote-tracking branch 'origin/dev' into sub_laptop_7 2026-10-31 14:45:31 +09:00
eomsangdon 87d8638a5b Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_7 2026-10-31 14:45:14 +09:00
eomsangdon 289c000cd7 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_7 2026-10-31 14:45:08 +09:00
eomsangdonandClaude Opus 5.5 042ea971b6 Merge origin/sub_laptop_7 into sub_laptop_8 — PLAN 704-4 규격 표 1차 · M02 일위대가 셈 짝 머리 설명 겹침 양쪽 살림(705-1 고정 줄 · 704-4 규격 열)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:44:09 +09:00
eomsangdonandClaude Opus 5.5 90273507c5 fix(규격 표 화면): 열 합계 한 자리 · 규격 줄 칸 폭 · B08 · B09 금액 = 열 수량 × 단가 시험 (PLAN 704-4)
- 격자 합계 줄 규격 칸 = 금액 칸처럼 소수 한 자리
- 규격 이름 · 열 이름 칸 6em — 좁은 카드에서 한 줄
- 임시 storage 시험 — 사본 셈 반영합 = 층 문서 그 열 셈(B08) · 대가 줄 금액 = 열 수량 × 단가(B09)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:41:13 +09:00
eomsangdonandClaude Opus 5.5 56e2c0b515 feat(규격 표 화면): 격자 규격 열 · M02 규격 표 편집 · 고르기 창 열 단추 (PLAN 704-4)
- 공용 격자 — 첫 호표 specs 면 수량 칸을 열마다(머리 「H 열」 · 기본 표시) · 합계 줄 그 칸 = 열마다 1 단위 합계
- M02 일위대가 카드 · 상세 — 격자 위 규격 줄(이름 · 열 이름 · 기본 · 열 지움 · [+ 열]) · 격자 수량 칸 열마다 고침(짝 셈 바로)
- 새 파일 M02_MasterTemplete_Labor_Spec.ts — 상세(700 줄)는 한 줄만 바꿈
- 고르기 창 — 규격 열 둘 이상이면 열 단추(열마다 합계) · [적용] = spec 붙은 참조 · 이름 뒤 「(H=열)」 · 열 하나 = 단추 없음
- 시험 704-3 격자 시험 두 줄 고침(laborGrid 넷째 인자 · Labor_Spec 이어 붙임) · 704-4 화면 자료 시험 둘

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:23:46 +09:00
eomsangdon 4290edb8bc Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_6 2026-10-31 14:20:37 +09:00
eomsangdonandClaude Opus 5.5 94cbf28f16 test(298-2): B09 [>] 시험 = 빈 줄 0 · 빈 줄 n(409) 두 갈래
- 까닭: 701-3 「빈 줄 있으면 409」 뒤 시험의 root = Path(".") 가 실제 저장소 뿌리로 내역을 셈 → 빈 줄 생겨 409 로 실패
- payload 를 갈아 끼워 빈 줄 0 = 단계 6 next · 빈 줄 3 = 409 + mark_stage 안 부름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:19:34 +09:00
eomsangdon a6b54f04e5 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_5 2026-10-31 14:18:05 +09:00
eomsangdon abb8729dbd Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_8 2026-10-31 14:17:55 +09:00
eomsangdon 1dc48b06ac Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-10-31 14:17:51 +09:00
eomsangdonandClaude Opus 5.5 3f2bb61bc7 Merge origin/sub_laptop_5 into sub_laptop_8 — PLAN 702-3 · B09 Router 머리 설명 겹침 양쪽 살림([업데이트] 703-3 · calc 702-3)
남은 것: test_298_2 test_b08_b09_next[B09] 가 701-3 빈 줄 409 와 부딪힘(root=Path(".") 로 실제 셈) — sub5 다음 일 첫 걸음에서 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:16:31 +09:00
eomsangdonandClaude Opus 5.5 425a4a9dfb feat(규격 열 고르기): summary_item spec · 사본이 고른 열 굳힘 · 같은 항목 = 열까지 · B07 한 장 = 항목 + 열 · 쓰는 열 지움 409 (PLAN 704-4)
- summary_item spec(파이썬 · TS 짝) — 없으면 기본 열 · 모르는 키 거절 그대로
- 사본 = 고른 열 수량을 손으로 굳힘 · manifest 규격 · same_item 에 spec(열 바꿈 = 새 사본 · 덮개 열쇠 S:폴더 그대로)
- B07 구조물 도면 키 = 항목 + 열 해시 — 열이 다르면 따로 장
- M02 일위대가 [저장] — 쓰는 사본 있는 열 지움 · 이름 바꿈 409
- 항목 목록 labor.규격(열 · 열마다 합계) — 고르기 창 열 단추
- 시험 test_704_4_spec 7(열 바꿈 뒤 덮개 남음 · 짝 잃음 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:16:06 +09:00
eomsangdonandClaude Opus 5.5 59a694a7ed feat(B09): 단가연결 즉시 금액(뒤에서 서버 셈) · [저장] 단추 · 옮길 때 물음(PLAN 702-3)
- 새 산근(로직 연결 · 입력값) = 연결 즉시 뒤에서 POST …/estimation/calc(화면 캐시로 셈 ·
  저장 안 함) · 차례 번호 · 옛 요청 끊기 · 오는 동안 그 줄 금액 칸만 「셈 중」 · 잠금 없음
  (새 `B09_Estimation_UI_Live.ts`) · 기준 = payload.user(서버가 그 셈에 쓴 값)
- 조작 중 셈 = 마지막 GET · [저장] 의 B08 원천 다시 씀(120 s · 0.6 → 0.15 s) · [저장] · GET 은 늘 새로 읽음
- [저장] 단추 = 왼쪽 아래 [초기화] 옆(B08 과 같은 부품 · 꼴) · 고침 없으면 알림만
- 단계 이동 · [>] = 저장 안 한 고침 있으면 공용 떠남 물음(취소 · 폐기 · 저장) — 옛 자동 저장 없앰
- 서고 사본 payload 의 user 받음(서버 기준) — 화면 캐시와 다른 로직 줄은 뒤에서 다시 셈
- 낡은 시험 고침: 481-2 · 298-2(자동 저장 → 물음 · [저장] 단추) · 184-3 · 304(단추 줄) ·
  204-2(user 받음) · 200-2(701-3 띠 · 702-1 펼침 뒤 셸 꼴)
- 확인 = test_702_3 3 · B09 관련 312 통과 · typecheck 0 · 검증_sub5 T6 11 누름 · 연결 뒤 0.32~0.49 s 금액

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:11:44 +09:00
eomsangdonandClaude Opus 5.5 3028801cd8 feat(일위대가 규격 열): 문서 「규격」 칸 검사 · settle 열 끼우기 짝 · 열마다 반영합 (PLAN 704-4)
- M02_Labor_Spec 새 파일 — 규격 칸 정리 · 열 끼우기 · 사본 굳히기 · 쓰는 사본 찾기
- settle(파이썬 · TS) 에 col — 그 열 수량이 있는 줄 = 손 자리 · 규격 없음 = 그대로
- 계산값 규격합 = 열마다 반영합(고르기 창 열 단추)
- 거울 시험 — 규격 열 · 모르는 열 = 기본 · 꼴 틀림 400 · 열 없는 옛 문서

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:10:04 +09:00
eomsangdonandClaude Opus 5.5 a24e0d622c feat(M02·B09 고정 줄): 일위대가 줄 「고정」 갈래 · B09 대가 fixed 줄 · 이음 단가 · 집계표 빠짐 수 (PLAN 705-1)
- M02 일위대가 줄 {고정: 이름 · 규격 · 단위 · 단가 · 이음(노임 · 자재 · 기계) · 출처} — 정리 · 셈(새 파일 M02_Labor_Fixed) · 줄 가름 짝 TS lineId
- 사본 단가 묶음 · B09 단가 책 수요에 이음 키
- B09 대가 줄 fixed — 이은 비목만 단가 책 값 · 못 이음 = 줄 단가 · 덮개 먹음 · 빈칸 안 됨(파이썬 · TS 짝)
- 집계표 — 이은 고정 줄 = 자원 셈 · 이음 없는 줄 = 빠짐 수(summary.fixed · 안내 「고정 줄 n」)
- B09 일위대가표 줄 꼬리표 「고정」 값 넘김
- 시험 test_705_1 12 · test_145_5 응답 칸 목록에 fixed(낡은 단정 고침)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:05:47 +09:00
eomsangdon 492dd05a9c Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-10-31 14:01:04 +09:00
eomsangdon d9d7724d94 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-31 14:00:59 +09:00
eomsangdonandClaude Opus 5.5 13de94b9dc knowledge(요구): 704-4 규격 표 설계 제안(sub7) 기록
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:59:37 +09:00
eomsangdon 66a9e71c26 Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_8 2026-10-31 13:57:53 +09:00
eomsangdonandClaude Opus 5.5 ac25855ae6 knowledge(요구): 요구 512 원문 기록 — 임상도 물음(브이월드 · 공공데이터포털)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 13:52:14 +09:00
eomsangdon 7d47c2cd09 Merge remote-tracking branches 'origin/main_laptop_1', 'origin/sub_laptop_7' and 'origin/sub_laptop_8' into sub_laptop_6 2026-10-31 13:49:23 +09:00
eomsangdon dbae5c3800 Merge remote-tracking branch 'origin/dev' into sub_laptop_6 2026-10-31 13:49:19 +09:00
eomsangdon e6a9cf336d Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_8 2026-10-31 13:47:31 +09:00
eomsangdonandClaude Opus 5.5 40b974c0c0 feat(B09 데이터 점검): [업데이트] 한 곳 — 단가 다시 굳힘 + 원본 바뀐 구조물 사본 재적용 · 미리 보기 창 (PLAN 703-3)
- POST …/estimation/prices = 연결된 구조물 사본 중 원본이 바뀐 것만 재적용(B09_Estimation_Refresh · 막힌 사본 = 알림 · 나머지 함) → 수요 다시 → 단가 책 다시 굳힘 · 덮개 그대로 · 답 notes 첫 줄 「단가 줄 N 바뀜 · 사본 K · 고친 칸 M 그대로」
- POST …/estimation/prices/preview = 바뀌는 단가 줄(새 · 값 바뀜) · 다시 가져올 사본 · 지킬 고친 칸 · 노임 판 전 → 후(쓰지 않음)
- 단가 책에 노임 판(labor_edition · 구분마다 M01 인력 판) · 책 견줌 book_diff(book_stale 도 같이 씀)
- UI_Check = [업데이트] 누름을 먼저 잡아 미리 보기 창 → [적용] 이면 원래 단추 일 · 호버 글
- 시험 test_703_3_b09_refresh(미리 보기 수 = 적용 수 · 판 · 재적용 원본 바뀐 것만 · 덮개 그대로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:46:28 +09:00
eomsangdonandClaude Opus 5.5 abe60c208e knowledge(요구): 요구 511 원문 기록 — 저전력 6 유지 · 한 시간마다 총량 대비 진행 표 하나
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 13:45:56 +09:00
eomsangdonandClaude Opus 5.5 1c1dc9c523 feat(B09 설계내역서 펼침): 우클릭 복사해 새 일위대가 · 내 자료집에 저장 · 어디 쓰였나 (PLAN 703-2)
- 복사 = 덮개 K:… 의 @copy {from · name · spec} + 원본 덮개째(B09 사용자 값 · 브레인 정함) — 셈 짝 Calc_Labor py·ts 가 원본 블록을 본뜬 새 호표(번호 끝 · 기본값 = 원본) · 이 줄 단가연결을 새 호표로 바꿀지 물음 · 복사본 지우기(잇는 줄 있으면 막음)
- 원본 없음 = 짝 잃음 알림 · 저장 다듬기 @copy 글만 · 서고 사본 원본 = prune 에서 지킴
- 내 자료집 = POST …/estimation/daega/personal → 개인 층 사전조합(B09_Estimation_Daega_Personal) — 넣을 값 · 고른 자재 · 줄 수량 덮개(손) 담음 · 품 · 단가 덮개 = 안 담은 수 알림 · 같은 이름 409 → 덮어쓰기 · 새 이름
- 어디 쓰였나 = 누른 줄 노임 · 자재 · 중기(그 밖 = 호표)를 쓰는 내역 줄 목록 → 누르면 그 줄로 · 펼침
- 새 파일 B09_Estimation_UI_Tabs_Bill_Fold_Menu.ts · 시험 test_703_b09_daega_copy(거울 · 임시 storage 길)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:35:44 +09:00
eomsangdonandClaude Opus 5.5 1599014504 fix(B09): 서고 사본 산근 = 산근 목록에 듦 · 대가 단가 채움(PLAN 702-3)
단가연결로 들여온 서고 사본(구조물조합 · 사전조합)의 단가산출 줄이 산근 목록에
안 들어 대가 블록 단가가 빈칸 — 산근 모음이 구조물 사본만 훑던 것 · 차례 =
대가 입력과 같게(구조물 → 서고 사본 → 수기 줄).
시험 test_702_3 서고 사본 산근 2 줄 · 대가 합 채움.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:33:49 +09:00
eomsangdonandClaude Opus 5.5 0377334f71 feat(일위대가 격자): 엑셀 꼴 공용 격자 부품 — M02 일위대가 카드 · 상세 · B09 일위대가표 같은 부품 (PLAN 704-3)
- 새 공용 부품 ui_template_hopyo_grid(.ts · .css) — 호표 머리 줄(이름 · 규격 · 단위 · 1 단위 합계) · 구성 줄(품명 · 규격 · 수량 · 단위 · 합계 · 노무비 · 재료비 · 경비 단가 / 금액 · 비고 · 구분 꼬리표) · 합계 줄 빈 줄 붉게 · 부품 모음 견본 칸
- M02 Labor_Grid — 문서 + 서버 calc 답 그대로(단가 = 기준 ÷ 배 · 금액 = 반영 · 합계 = 반영합 · 수량 없음 줄 = 빈 줄) · Labor_Detail 맨 위 격자(카드 · 상세 같이) · 읽기만 카드 = 격자만
- B09 일위대가표(호표보기 · 박스보기 바탕 줄) = 같은 부품 · 줄 값 · 이동 · 줄 꼴 그대로 + 구분 칸 · 머리 1 단위 합계
- 시험: test_704_3 새로 · B09 표 시험 다섯(155 · 157-4 · 201-1 · 202-1 · 304)은 격자 파일을 이어 붙이게 고침(줄 글이 공용 부품으로 옮김) · hopyo_screen 상세 줄 수 693 → 700

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:29:00 +09:00
eomsangdon 5cb947d46c Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-10-31 13:28:09 +09:00
eomsangdon afe6d704f6 Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_5 2026-10-31 13:27:38 +09:00
eomsangdon ecddace47b Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_5 2026-10-31 13:27:37 +09:00
eomsangdonandClaude Opus 5.5 43c34cfa01 Merge origin/sub_laptop_5 into sub_laptop_8 — PLAN 701 · B09 껍데기 겹침 양쪽 살림(띠 위 · 표 · 펼침 · 안내)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:26:34 +09:00
eomsangdonandClaude Opus 5.5 9246250629 feat(M02): 일위대가 수량 안 넣은 줄 경고 · 복사 생성 이름 「… 복사」(PLAN 701-4)
- 일위대가 카드 · 상세 합계 줄 — 수량산출 빈 줄 수 「수량 안 넣은 줄 n — 합계에서 빠짐」(좁은 카드에서 제 줄 · 줄바꿈)
- 복사 생성(나무 copyIn) = 대상 복사이름 → 새 이름 「원래 이름 복사」 · 또 하면 「… 복사(1)」 · 그냥 붙여넣기는 그대로
- 시험 test_m02_tree_paste(임시 storage) 한 건 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:22:51 +09:00
eomsangdonandClaude Opus 5.5 df6bc10237 feat(B09): 원가계산서 = 아는 금액까지 + 「빈 줄 n · 미완성」 띠 · 인쇄 · 확정은 빈 줄 0 일 때만(PLAN 701-3)
- 설계내역서 sums = 빈 줄이 있어도 아는 비목 합 + blank(파이썬 · TS 짝 · 관급은 그대로)
- 총괄 · 원가계산서 표 위 공용 띠(경고) — 탭 표 banner 칸
- [인쇄 미리보기] · [>] = 빈 줄 있으면 알림 뒤 머묾(DRAFT.blocked) · 서버 확정(complete) = 정본으로 다시 셈 · 빈 줄 있으면 409
- 설계내역서 안내 글 줄 고침 · 낡은 시험(sums None · 패널 줄 · [>] 흉내) 갱신 · 시험 test_701_3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:09:29 +09:00
eomsangdon 595d6415eb Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_6 2026-10-31 13:08:57 +09:00
eomsangdon d51f331f11 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_8 2026-10-31 13:07:09 +09:00
eomsangdon dbefd28db1 Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into sub_laptop_7 2026-10-31 13:06:38 +09:00
eomsangdon 64d478c8b6 Merge remote-tracking branch 'origin/dev' into sub_laptop_7 2026-10-31 13:06:36 +09:00
eomsangdonandClaude Opus 5.5 b7bc6087a7 fix(B09 설계내역서 펼침): 펼친 표 줄 번호 표식 — 두 번 눌러 고침 · 우클릭 [품셈 값으로] · 풍선이 줄을 찾음 (PLAN 702-1)
- 공용 표는 칸 누름(onCellClick)이 있어야 줄에 data-i 를 붙임 — 빈 칸 누름을 줌

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:03:36 +09:00
eomsangdonandClaude Opus 5.5 508e1ec34d feat(M02 기준 목록): 로직 고르기 창 = 기준 목록 표 · 줄 누름 = 계산서 보기 → [담기] (PLAN 704-1 · 704-2)
- 고르기 창 단가산출 줄 → 공용 표(품명 · 규격 · 단위 · 금액 · 출처) · 공종 거름 · 찾기 = 공용 matchLogic(규격 · 실무 낱말 별칭까지) · GF 번호 숨김 · 창 폭 960
- 기준 목록이 오기 전 · 서버 셈 도중 = 로직 줄(금액 빈칸) · 서고 · 덧줄 목록은 그대로
- 계산서 창 `M02_MasterTemplete_Labor_Sheet` — 맨 위 출처 · 노임 판 · 「이름 | 노임 × 품 | 금액」 줄 · 비목 소계 · 계(계산 줄 부품) · 곁에 원문 띠 · [담기] = 지금 담기 동작 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:02:07 +09:00
eomsangdonandClaude Opus 5.5 13e5980732 feat(M02 기준 목록): 대표 규격 펼친 기준 목록 · 계산서 서버 길 — 금액 = M01 엔진 · 파생 저장본 (PLAN 704-1 · 704-2)
- `M02_Logic_Standard` — 로직 하나를 대표 규격(고르기 값 전부 · 표 찾기 조건 수 = 표 줄마다 · 지역 = 자동값)으로 펼친 줄 · 품명 · 규격 · 단위 · 금액(엔진 계) · 출처 · 공종 · 실무 낱말 별칭(석축 → 돌쌓기)
- 목록 = 서버가 뒤에서 한 번 셈(프로세스 4 · 약 90 s) → storage `_derived/m02_logic_standard/` · 열쇠 = 마스터 내용 지문 · 셈 도중 빈 줄 + 준비 false
- 계산서 = `master_text.lines` 줄 · 비목 소계 · 계 · 맨 위 출처 · 노임 판 · 규격 축 아닌 입력 400
- 길 `/api/m02/logic-lib/standard` · `/logic-lib/sheet` — 로그인만(일반 사용자도)
- 시험 `test_704_logic_standard.py` — 세 항목 금액 = M01 시험 계산 · 계산서 계 = 엔진 값 · 석축 찾기 · 저장본

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:01:54 +09:00
eomsangdonandClaude Opus 5.5 21bc4f856a feat(B09): 첫 화면 = 설계내역서 · 빈 줄 수 하나(PLAN 701-2)
- 첫 탭 = 설계내역서(빈 원가계산서 아님) · 엑셀 탭 차례는 그대로
- 빈 줄 = 설계내역서 금액 모르는 직접공사 항목 줄(blankRows) — 설계내역서 안내 · 데이터 점검 · 집계표 넷이 같은 수 · 같은 낱말
- 데이터 점검 = 「빈 줄 n」(줄마다 하나 · 까닭 모음) + 「그 밖 확인 n곳」(호표 · 재료 · 탭 · 키마다 하나)
- test_203_1 낡은 값(첫 탭 · 「연결 없음 n곳」) 갱신 · helper 에 593-1 시각 글 stub(dateTimeText 없어 깨져 있던 것)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:51:05 +09:00
eomsangdon c847489e4d Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into main_laptop_1 2026-10-31 12:48:23 +09:00
eomsangdonandClaude Opus 5.5 970211bb5f feat(B08 구조물도): 카드에 B09 원가에서 고친 일위대가 칸 수 표시 — 노임 새 판 · 재적용 뒤에도 덮개 남음 (PLAN 702-4)
- 사본 셈 길(/items/{폴더}/calc) 답에 B09고침 = 그 사본 블록 + 부른 블록 덮개 수(B09 cover_count)
- B08 구조물도 조합 칸 = 「B09 원가에서 고친 일위대가 칸 N — 이 카드 금액은 고치기 전 사본 값」(카드 셈 길이 달라 금액 반영 대신 표시 · 브레인 정함)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:41:37 +09:00
eomsangdonandClaude Opus 5.5 8d80e1f680 feat(B09 설계내역서): 줄 펼침 — 줄 누르면 표 아래 일위대가 구성 · 셀에서 고친 칸 덮개 · 품셈 값 곁 · [품셈 값으로] (PLAN 702-1 · 702-2)
- 새 파일 B09_Estimation_UI_Tabs_Bill_Fold.ts — 일위대가(dg) 연결 줄만 · 값 = 일위대가표와 같은 셈(daega_blocks) · 부른 대가 3단까지 「ㄴ」
- 칸 = 품명 · 규격 · 품·수량 · 단위 · 노 · 재 · 경 단가 · 금액 · 품셈 값 · 비고 · 합계 줄
- 품 · 수량 · 단가 두 번 눌러 고침 → 캐시 daega_cover 즉시 · 짝 셈 다시 · 고친 칸 색(is-edited) · 풍선 = 품셈 값 · 우클릭 · [모두 품셈 값으로] · 빈칸 입력 = 되돌림
- 고리 = UI_Tabs_Bill fold 한 줄 · BillTable.fold 형 · Shell 표 아래 자리 + computeLabor 에 덮개 넘김

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:41:36 +09:00
eomsangdonandClaude Opus 5.5 969d7f74f3 feat(B09 일위대가): 고친 칸 덮개 셈 — 사용자 값 daega_cover · 블록 cover · 줄 cid · 파이썬·TS 짝 (PLAN 702-2)
- 덮개 자리 = B09 사용자 값(user_values.json · 화면 캐시와 같은 user) {블록 cover: {줄 cid: {qty · 노 · 재 · 경 단가}}} · 원본 층 · 사본 파일 안 건드림(브레인 승인)
- 블록 cover = 조합 S:사본 폴더 · 서고 L:폴더 · 사전조합 C:참조 · 품셈 P:로직+입력 해시 · 줄 cid = 줄 정체(코드 · 산근 로직 · 부른 대가 로직) + n번째 — 내용 해시 밖(호표 id 그대로)
- 셈 짝 Calc_Labor.py · .ts — 고친 칸만 셈 값 대신 · 줄 base(품셈 값) · covered · 짝 잃은 덮개 daega_orphans(지우지 않음) · 데이터 점검 알림
- [저장] = 서버가 덮개 입력값만 받아 다시 셈(Store.clean_cover 수만) · 노임 새 판 · [재적용] 뒤에도 남음
- B08 구조물도 카드용 고친 칸 수 cover_count
- 시험 test_702_b09_daega_cover(거울 · 판 바꿈 · 재적용 꼴 · 다듬기) · test_145_2 _ts 덮개 인자

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:41:22 +09:00
eomsangdonandClaude Opus 5.5 80bae255bd fix(B08·B09): 석축 · 옹벽 길이 → 구조물 집계표 열 · 설계내역서 곱하기(PLAN 701-1)
- B08 구조물 집계표 — 구조물 표 형상 wall(옹벽 · 돌쌓기 · 큰돌쌓기 · 흙막이) 높이마다 길이 m 열 · 빈 높이 = 「미입력」 열 값 빈칸 · 사본 없으면 안 셈(미연결)
- 응답 types 에 벽 종류 — 화면 조합 열에서 뺌
- B09 설계내역서 — 벽 열 = 2.3 기타구조물 · owners 수량 × 대가
- test_120 종류 집합 낡은 값 갱신 · 시험 test_701_1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:31:45 +09:00
eomsangdon 935bd7d04e Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_8' into sub_laptop_5 2026-10-31 12:22:08 +09:00
eomsangdonandClaude Opus 5.5 d772e15e0d knowledge(요구): 요구서 내역서 개편 — 고정형 답 · 원칙 · 요구 14 · 정함 · PLAN 701~706
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:20:32 +09:00
eomsangdonandClaude Opus 5.5 e78ae8e1a4 knowledge(요구): 요구 510 원문 기록 — 랩탑 저전력 코어 6 개 사용 승인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 12:19:55 +09:00
eomsangdonandClaude Opus 5.5 bcf8e0737b knowledge(요구): 내역 8 원문 기록 — 고정형 물음 · 익숙한 꼴 · 계획 · 요구서 · 경쟁사 결과물 불러오기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:19:05 +09:00
eomsangdon 75e2d65e29 Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_8' into main_laptop_1 2026-10-31 11:23:41 +09:00
eomsangdonandClaude Opus 5.5 88ec2da193 feat(도구): 전국 미리 계산 두 PC 나눔 — 몫 · PC 별 기록 · CPU 상한 · 저전력 코어 지정 (PLAN 618-1 · 요구 507~509)
- --part A-B/N = 도엽 번호 crc32 % N 몫 · 지형 단계가 0차곡 이웃(목록 밖 · 띠 넘음 포함)까지 몫대로 셈
- 0차곡 = 남의 몫 이웃 지형이 창고에 없으면 미룸 · --defer-wait 초 뒤 다시
- 진행 기록 = _precompute_progress_<컴퓨터 이름>.json · 옛 한 벌은 성공만 읽어 이어 받음 · --status 모든 PC 합침 · 창고 실물 수
- --cpu-cap = 전체 CPU 평균이 넘으면 새 도엽 안 줌 · --e-cores --e-workers = 일꾼 앞선 몇 명만 그 논리 CPU 지정
- 죽은 잠금 치움 = 자기 몫 도엽만 · --root 시험용 창고 자리
- test_612: 지형 단계 이웃 일감 덧 · 기록 파일 이름 바뀜에 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:22:24 +09:00
eomsangdonandClaude Opus 5.5 fc11a235dd knowledge(요구): 내역 5 · 6 · 7 조사 — 설계 연동 고리 · 엑셀 붙여넣기 · 기초자료 만들기 실측 · 기준 단가산출 대조 · 보여 주는 꼴 종합
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:18:57 +09:00
eomsangdonandClaude Opus 5.5 74f13122a3 chore(계획): PLAN 617 이관 — 0차곡 D8 평지 고침 · 라플라스 홀짝 공용 판 · 비트 동일 (요구 504~506)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 11:15:35 +09:00
eomsangdon 1ed9e9203d Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-10-31 11:13:26 +09:00
eomsangdon 25290e5384 auto: 2026-10-31 11:04 (ESD_LAPTOP) 2026-10-31 11:04:06 +09:00
eomsangdonandClaude Opus 5.5 c9829089de knowledge(요구): 내역 7 원문 기록 — 경쟁사 = 내역 전용 구분 · 기준 단가산출 만드는 법 참고
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:03:23 +09:00
eomsangdon 553d62c0df Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_8' into sub_laptop_1 2026-10-31 11:01:24 +09:00
eomsangdonandClaude Opus 5.5 969a3fad03 knowledge(요구): 요구 509 원문 기록 — 랩탑 저전력 + 데스크탑 함께 진행 · 데스크탑 전체 70 % 상한(여유 30 %)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 11:00:53 +09:00
eomsangdonandClaude Opus 5.5 899e7f9584 knowledge(요구): 요구 508 원문 기록 — 랩탑 저전력 코어 안 씀 · 데스크탑 자원 검토
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 10:54:12 +09:00
eomsangdonandClaude Opus 5.5 c59cc1fc47 knowledge(요구): 내역 6 원문 기록 — 품셈 복사 · 수정 화면을 어떤 꼴로 보여 줄지
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:52:14 +09:00
eomsangdon ab8be542f4 Merge remote-tracking branches 'origin/sub_laptop_4' and 'origin/sub_laptop_8' into sub_laptop_5 2026-10-31 10:48:20 +09:00
eomsangdonandClaude Opus 5.5 e789b8851c knowledge(요구): 내역 5 원문 기록 — 설계 기초자료 만들기 · 엑셀 붙여넣기 습관
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:48:06 +09:00
eomsangdonandClaude Opus 5.5 74edf736a8 knowledge(요구): 내역 4 원문 기록 — 보고는 아티팩트로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:35:52 +09:00
eomsangdonandClaude Opus 5.5 e181ae07d9 knowledge(요구): 요구 507 원문 기록 — 동시 계산 3 개 더 · CPU · 완료 시간 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 10:34:23 +09:00
eomsangdonandClaude Opus 5.5 219ca43a63 knowledge(요구): 내역 3 화면 분석 — EST · STmate 와이어프레임 · 아이슬로 실측 · 조합안
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:34:04 +09:00
eomsangdonandClaude Opus 5.5 0055f494a6 perf(공용 지형): 라플라스 홀짝 네 덩이 · 거리 비례 copyto · 폐합 채움 수위 상자 창 — 저장본과 비트 동일 (PLAN 617-2 · 요구 504 · 506)
- 새 common_util_terrain_laplace.py — B04 build_laplace 짝(같은 덧셈 차례 · float32) · build_contour_surface 가 부름
- 폐합 채움 = 수위 칸 상자 + 1 칸 덧댐 안 채움 · 묶음 find_objects 창 · 차례 그대로 · 쓰지 않는 _window 지움
- TERRAIN_ALGO 1 그대로 · B 파일 안 고침
- 짝 시험 test_617_2_laplace_mirror.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:27:31 +09:00
eomsangdonandClaude Opus 5.5 c5a22d6407 perf(D03 0차곡): D8 평지 이어 감 = 같은 높이 이웃 표 한 번 · 안 흐르는 후보 칸만 돌림 — 결과 비트 동일 · 장당 2~27 배 (PLAN 617-1 · 요구 504 · 506)
- 방향 차례 · 갱신 차례 그대로 · ZERO_ORDER_VERSION · 창고 열쇠 그대로
- 시험 test_617_1_d8_flat: 옛 꼴과 합성 격자 대조 + 창고 0차곡 도엽 열 장 다시 셈 = 저장본(읽기만)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:25:49 +09:00
eomsangdon 2f90727ca9 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_8' into sub_laptop_3 2026-10-31 10:19:19 +09:00
eomsangdonandClaude Opus 5.5 4d85460779 knowledge(요구): 요구 506 원문 기록 — 617-1 · 617-2 진행 · 완료 시간 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 10:18:37 +09:00
eomsangdonandClaude Opus 5.5 1b1d5daeda knowledge(요구): 요구 504 분석 기록 — 미리 계산 시간 쪼개기 · D8 · 라플라스 · E 코어 · GPU 득실
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 10:13:43 +09:00
eomsangdonandClaude Opus 5.5 3a3e0fea2d knowledge(요구): 내역 3 테스트 사용자 질문지 초안 — 화면 · 손버릇 7문항
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:58:38 +09:00
eomsangdon 2d02e17b30 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_5 2026-10-31 09:56:59 +09:00
eomsangdonandClaude Opus 5.5 535222e345 knowledge(요구): 내역 3 원문 기록 — 원칙 동의 · 화면 분석 · 질문지 · 조합 고민
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:52:41 +09:00
eomsangdonandClaude Opus 5.5 338a497e2a knowledge(요구): 요구 505 원문 기록 — CPU 그대로 · GPU 갈래 만드는 시간 확인만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 09:40:27 +09:00
eomsangdonandClaude Opus 5.5 1aa3197334 knowledge(요구): 요구 503 · 504 원문 기록 — 0차곡 창고 1 초 미만 물음 · 미리 계산 단축 · GPU 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 09:35:19 +09:00
eomsangdonandClaude Opus 5.5 19a13054b7 knowledge(요구): 내역 1 조사 — EST · STmate 사용성 · 아이슬로 현황 · 종합 방향 후보
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:33:44 +09:00
eomsangdonandClaude Opus 5.5 4c8af68682 knowledge(요구): 요구 502 원문 기록 — 616 답 기다리는 대상 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 09:27:45 +09:00
eomsangdon 5748051dae auto: 2026-10-31 09:22 (ESD_LAPTOP) 2026-10-31 09:22:53 +09:00
eomsangdonandClaude Opus 5.5 3ad0aafd84 knowledge(요구): 내역 2 원문 기록 — 오솔길 비교 제외
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:18:02 +09:00
eomsangdonandClaude Opus 5.5 7fcf467880 knowledge(요구): 내역 1 원문 기록 — 내역서 영역 경쟁사(EST · STmate) 사용성 조사 착수
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:11:15 +09:00
eomsangdon 298f455da8 Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_8 2026-10-31 09:10:19 +09:00
eomsangdonandClaude Opus 5.5 a2ef417661 feat(도구): 등고선 벡터 조각 미리 만들기 — z8~12 · 이어 하기 · 일꾼 둘 · 낮은 우선순위 (PLAN 615-3)
- tools/precompute_contour_tiles.py — 조각 목록 = 612 전국 도엽 목록이 닿는 z/x/y(z8~12 2,923 조각) · 창고에 있으면 건너뜀 · --status 남은 수 · 크기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:06:55 +09:00
eomsangdonandClaude Opus 5.5 8ce7ef6cbe feat(D 예정노선): 등고선 = 줌별 벡터 조각 — 서버 조각 · 공용 창고 · 화면 고정 소스 (PLAN 615-1 · 615-2 · 요구 496)
- 서버: 새 common_util_contour_tiles — 조각(z8~15) = 전국 등고선 상자 읽기 · 줌 간격 거름 · 단순화 · 3857 · MVT(겹 contour · h · k) · 창고 storage/_shared/contour_tiles/v1_<지문>/z/x/y.pbf · 잠금 · .part 바꿈 · 빈 조각 저장
- 줌 간격: z8 1000 · z9 500(화면 꼭짓점 z13 이하 실측) · z10 200 · z11 100 · z12 50 · z13 25 · z14 10(+25) · z15 5 m
- 길: GET /api/projects/{id}/plan/contour/{z}/{x}/{y}.pbf
- 화면: contourTileSource(위성처럼 고정 · minzoom 8 · maxzoom 15) · 등고 겹 셋 = 벡터 조각 · 받는 동안 서클 · GL 이면 606 등고 도엽 조각 안 받음(옛 캔버스는 그대로) · 주곡선 간격 셈 걷음
- 시험: test_615_contour_tiles

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:06:48 +09:00
eomsangdonandClaude Opus 5.5 7016cd8d62 knowledge(요구): 요구 497 분석 기록 — 급경사 · 0차곡 느린 까닭 실측 · 고칠 길
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 08:59:23 +09:00
eomsangdonandClaude Opus 5.5 74defc547f knowledge(요구): 요구 501 원문 기록 — 한시 브레인 sub8 · 작업 창 sub5~7 지정
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 08:57:29 +09:00
eomsangdonandClaude Opus 5.5 2decb9e359 knowledge(요구): 요구 500 원문 기록 — 등고선도 줌아웃 때 오래 걸림 · 검증 지시
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 08:50:45 +09:00
eomsangdonandClaude Opus 5.5 0952cb4dac knowledge(요구): 요구 499 원문 기록 — 줌별 속도 · 줌아웃 밀도 · 미리 계산 끝난 구역 시도 기준 전후 비교 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 08:44:53 +09:00
eomsangdonandClaude Opus 5.5 89bf0c1a4f knowledge(요구): 요구 498 원문 기록 — 조각 포장 부품(mapbox-vector-tile) 씀 동의
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 08:41:13 +09:00
eomsangdonandClaude Opus 5.5 7b20189f74 knowledge(요구): 요구 497 원문 기록 — 미리 계산 목적 = 속도 · 급경사 · 0차곡 화면 갱신 느린 까닭 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 08:36:29 +09:00
eomsangdonandClaude Opus 5.5 39b5ddf697 knowledge(요구): 요구 496 분석 기록 — 등고선 줌별 벡터 조각 실측 · 설계 · 결정 거리
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 08:16:24 +09:00
eomsangdonandClaude Opus 5.5 555e9445de knowledge(요구): 요구 496 원문 기록 — 등고선 줌 따라 해상도 · 보는 화면만 표시(위성처럼)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 07:50:51 +09:00
eomsangdonandClaude Opus 5.5 725ffd805e knowledge(요구): 요구 495 원문 기록 — 전국 미리 계산 CPU 35 % 까지 · 시간 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 06:53:35 +09:00
eomsangdonandClaude Opus 5.5 4365ae9c83 chore(계획): PLAN 614 이관 — 다른 좌표계 = 미리 계산 지형 옮겨 담기 (요구 494)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 06:41:19 +09:00
eomsangdonandClaude Opus 5.5 f97c8975a3 feat(공용 지형): 띠 밖 좌표계 = 띠 지형 쌍선형 옮겨 담기 (PLAN 614-1 · 요구 494)
- ensure_sheet_terrain: 도엽 띠(5185~5187) 아닌 좌표계면 띠 저장본(없으면 띠로 먼저 셈)을 그 좌표계 2 m 격자로 쌍선형 옮겨 담아 저장 · 열쇠 via = 띠
- 이미 직접 센 저장본 그대로 · REPROJECT_FROM_BAND 한 줄로 끔 · 가장자리 = 창고에 있는 이웃 띠만 · 없으면 직선 연장
- 문턱(브레인) = 참값(등고선 위 점) 대비 오차 · 옮김이 더하는 95 % ≤ 0.10 m · 최대 ≤ 직접 최대
- 시험 test_614 새로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:39:15 +09:00
eomsangdonandClaude Opus 5.5 18457471ba knowledge(요구): 요구 494 원문 기록 — 다른 좌표계 때 미리 계산 다시 쓰는지 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-31 06:20:03 +09:00
eomsangdonandClaude Opus 5.5 67a0b1a385 chore(계획): PLAN 609 · 611 · 613 이관 — B 하강장 공용 · 도곽 번호 · 받기 상한 도엽 36 · 고름 대비 (요구 493)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 23:43:06 +09:00
eomsangdon 675671eb47 Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_2' and 'origin/sub_laptop_4' into main_laptop_1 2026-10-30 23:12:18 +09:00
eomsangdonandClaude Sonnet 5.5 eb97ddc73c feat(D 예정노선): 고름 강조 검은 테 한쪽 4 px (PLAN 613-2 · 요구 493)
- 위성 켬 밝은 바탕에서도 윤곽 대비 하위 10 % 4.7 : 1(옛 줄만 기준 2.96)
- 시험 551_2 에 바탕 밝기 전 구간 ≥ 3 : 1 지킴 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 23:11:42 +09:00
eomsangdonandClaude Sonnet 5.5 db5a2f0408 feat(D 예정노선): 받기 상한 = 칸 500 만 → 공용 창고 도엽 36 장 (PLAN 613-1 · 요구 493)
- 한 번에 이어 붙이는 도엽 수 36 장(6 × 6) 넘으면 413 + 알림 글(몇 장 · 줄이는 법)
- 범위 보기 응답에 store_sheets · max_sheets
- 시험 530_1 · 530_2 갱신

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 23:11:41 +09:00
eomsangdonandClaude Opus 5.5 59ae735eda feat(B04 배수): 하강 방향장 도엽별 공용 창고 — 도엽 + 여유 100 m 로 한 번 짓고 이어 붙여 읽기만 (PLAN 609-1 · 요구 486 · 493)
- 새 파일 B04_PreProcess_Engine_Watershed_DescentShared.py — storage/_shared/terrain/<도엽>/descent_<좌표계>_1m.npz(칸 코드 int8 · 밴드 표고) + 열쇠(판 · 여유 · 규칙 · 전국 파일 지문 · zip 해시) · 짓는 법은 build_contour_descent 그대로 · 원천 = 전국 등고선 · 빠진 곳 = 607 도엽 zip
- 배수 확장 source_for — 저장소 셋째 값(좌표계)이 있으면 공용 길 · 못 지으면 옛 프로젝트 길 · 2 값 저장소(시험)는 그대로
- 라우터 두 곳 — _prepare 가 좌표계를 함께 넘김
- 공용 지형 창고 — 좌표 변환 6 곳이 번호 없는 좌표계 글(PRJ WKT)도 받음 · 정수 입력 그대로
- 시험 test_609_1 — 도엽 넷 이어 붙임 = 통째 짓기 · 두 번째 짓기 0 · 원천 바뀌면 다시 · WKT · 확장 공용 길 = 프로젝트 길 · 실패 시 프로젝트 길

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 23:09:57 +09:00
eomsangdonandClaude Opus 5.5 4220eb6e0a feat(공용 지형): 전국 미리 계산 일괄 도구 — 지형 · 0차곡 누적 · E07 도엽마다 (PLAN 612-1 · 요구 493)
- tools/precompute_terrain.py — 도엽 목록(전국 등고선 꼭짓점이 닿는 도엽 · 길이 · 높이 차 · 지문 같으면 저장본) · 산지 먼저 · 단계 dem → zero → e07 · 이어 하기(진행 기록) · 일꾼 수 · 낮은 우선순위 · 범위 고름(--sheets 앞자리 · --box · --limit · --min-relief) · 죽은 잠금 치움
- 셈은 공용 창고 · D 0차곡 · E07 함수만 부름(원자 쓰기 · 열쇠 건너뜀)
- 시험 test_612_precompute 6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:58:17 +09:00
eomsangdonandClaude Sonnet 5.5 32b2d82790 fix(B04 도엽): 도곽선 N3L_H0010000 읽기(없으면 옛 방식) · 공용 창고 prune 막음 (PLAN 611-1 · 요구 486 · 493)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 22:52:00 +09:00
eomsangdonandClaude Opus 5.5 0a54d52fff knowledge(요구): 요구서 486 6장 — 전국 미리 계산 · 보류 둘 진행 · PLAN 609 · 611 진행 · 612 · 613 (요구 493)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 22:47:00 +09:00
eomsangdonandClaude Opus 5.5 55bd94691f knowledge(요구): 요구 493 원문 기록 — 보류 둘 브레인 제안대로 진행 · 전국 등고 최신 · 데스크탑 정션 · 전국 급경사 · 0차곡 · 배수 미리 계산 확인 · 반영
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 22:46:12 +09:00
eomsangdonandClaude Opus 5.5 1fd6d822df chore(계획): PLAN 604 · 605 · 606 · 608 · 610 이관 — 영역 루틴 · 임도망 목록 · 도엽 조각 · 공용 지형 읽기 · E07 미리 계산 (요구 475~492)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 22:34:56 +09:00
eomsangdon 715740c806 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-30 22:32:09 +09:00
eomsangdonandClaude Opus 5.5 b6619be9ce feat(D 예정노선): 도엽 조각 검증 창구 — 마지막 보기 경위도 상자 (PLAN 606-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:31:29 +09:00
eomsangdonandClaude Opus 5.5 bc1d5165ff feat(D 예정노선): 도엽 조각 — 공용 창고 storage/_shared/sheets · 보이는 도엽만 받기 (PLAN 606-1 · 606-2 · 요구 477)
- 새 D03_PlanAnalysis_SheetTiles.py — 도엽 도곽으로 자른 선 겹 조각(등고 · 계곡 = 전국 등고선 · 세류 · 물 면 · 도로 · 건물 · 지명 = 도엽 zip) · zip 은 프로젝트 · 공용 지형 창고에서 복사(원본 안 지움) 뒤 받기 · 빈 도엽 = 빈 조각 · 받기 실패 저장 안 함 · B04 함수 부르기만
- 라우터 길 둘 — 보이는 도엽 목록(bbox) · 조각 하나
- 새 UI_SheetTiles.ts — 보기 멈춘 뒤 보이는 도엽 · 없는 조각만 하나씩 · 12 장 이하 다 · 13~30 계곡선만 · 넘음 숨김 · 서클
- UI_Layers — 도엽 선 겹 원천을 조각으로 · 시험 606 새 · 475 옛 겹 길 단언 바꿈

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:14:11 +09:00
eomsangdon a90c03ac7a Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-10-30 22:13:52 +09:00
eomsangdonandClaude Opus 5.5 fc4f3ef1ae feat(E07 광역): 지형 = 공용 지형 창고 · 도엽마다 경사 · 집수면적 · 기본 물성 SI 미리 · 물성 바꿈 = 칸 계산만 (PLAN 610-1 · 요구 487 · 488)
- 새 E07_StabArea_Sheet — 도엽 지형 + 여유 400 m 로 경사 · D∞ 집수면적 → 창고 도엽 폴더 e07_<epsg>_<격자>m.npz · 기본 물성(마스터 E02 붕적토) SI = e07si_ · 열쇠 = 판 + 여유 + 이웃 도엽 지형 열쇠
- 라우터 — LAS DTM 복사본 안 씀 · 노선 띠 상자 → 도엽 이어 붙임 · 좌표계 = 동결 DB 지형 모델 crs_epsg · 물성이 기본과 같으면 미리 셈 SI · 저장 상자 = 띠 안 지형 있는 칸
- 엔진 — stability() 로 나눔 · 안 쓰는 resample_dem 지움
- E07 안내 문구 — 지형 = 전국 등고선 공용 지형
- 시험 — 536_2 가짜 복사본 DTM → 가짜 창고(LAS 안 씀) · 새 610_1(도엽 이음 = 통째 · 위쪽 유역 안 잘림 · 두 번째 · 물성 바꿈 = 흐름 안 셈 · 지형 열쇠 바뀌면 다시)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:13:07 +09:00
eomsangdon a3f4576802 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_2 2026-10-30 22:05:04 +09:00
eomsangdonandClaude Opus 5.5 b2eb18b7af perf(D 예정노선): 지형 받기 창고 새 도엽 함께 셈 (PLAN 608-1)
- 받은 범위에 닿는 창고 없는 도엽을 스레드 넷까지 함께 셈 · 진행 = 도엽 n/m 매
- 용화 처음 진입 133.4 s → 67.7 s(차례 셈 대비)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:04:39 +09:00
eomsangdon f7e149ed56 Merge remote-tracking branches 'origin/sub_laptop_3' and 'origin/sub_laptop_4' into main_laptop_1 2026-10-30 22:03:58 +09:00
eomsangdonandClaude Opus 5.5 28c7afe697 feat(D 예정노선): 0차곡 흐름누적 = 도엽별 공용 창고 (PLAN 608-2 · 요구 486)
- 도엽 + 여유 200 m(창고에 있는 도엽만 · 없는 이웃 = 출구)로 셈 · storage/_shared/terrain/<도엽>/zero_acc_<epsg>_2m.npz
- 열쇠 = 쓴 도엽 지형 열쇠 · 이웃 지형이 새로 생기면 다시 셈
- 0차곡 겹 = 도엽 누적 이어 붙임 · 임계 바꿈은 칸 고르기 · 선 뽑기만 · 창고 격자 아닌 옛 DEM 은 통째 셈
- 새 시험 608 = 조각 대 통째 0차곡 칸 차 0 · 저장 · 이웃 들어옴 다시 · 임계 바꿈 누적 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 21:55:24 +09:00
eomsangdonandClaude Opus 5.5 707552ec9a feat(D 예정노선): 지형 받기 = 공용 지형 창고 도엽 이어 붙임 (PLAN 608-1 · 요구 486)
- 병합 등고선 → 받은 범위 통째 DEM 걷음 · load_terrain(받은 범위 · 2 m) 이어 붙여 models_plan/dtm_sheet_laplace.npz · 미리보기 · 평활본 같은 꼴
- 열쇠 = 이어 붙인 값 지문 · 같으면 다시 안 씀(급경사 · 음영 지문 그대로)
- 창고 진행 = 5/5 단계 진행률에 이어 붙임 · 도엽 zip = 보조 겹만(받기 실패는 경고)
- 시험 530_1 = 가짜 창고 · 값 같음 안 다시 씀 · 값 바뀜 다시 씀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 21:55:17 +09:00
eomsangdonandClaude Opus 5.5 9ae25a9577 merge(D 예정노선): dev 받기 — 604 영역 루틴(fetchLayer · areaTurn · load(have) · track) 과 605 올린 임도망 묶음(applyRoads · deleteRoad · pickRoad) 함께 살림 · 옛 haveTag · sheetLayer 뺌 (PLAN 605 · 요구 475 · 476)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 21:54:36 +09:00
eomsangdon 04a9d194cf Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_3 2026-10-30 21:52:17 +09:00
eomsangdonandClaude Sonnet 5.5 40cecf27a2 feat(D 예정노선): 올린 임도망 묶음 쌓기 · 목록(고르기 · 지움) · 지도 밝은 분홍 + 검은 테 (PLAN 605-1 · 605-2 · 요구 476-1)
- 서버 — 올릴 때마다 묶음 하나(`D03_PlanAnalysis/임도망도/<번호>`) · 옛 한 파일 꼴 = 묶음 legacy · 합친 값 + 묶음 줄 · 목록 GET · 묶음 DELETE · 20 묶음까지
- 화면 — 위치 칸 목록(묶음 줄 · 펼치면 선 줄 · 고름 = 지도 강조 + 맞춤 · 지움 = 물음 뒤) · 색 주황 → 밝은 분홍(`--draw-map-forest-road-file`)
- 시험 584_3 — 묶음 쌓기 · 지움 · legacy · 20 묶음 · 화면 정의

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 21:49:20 +09:00
eomsangdonandClaude Opus 5.5 130763d991 knowledge(요구): 요구 492 원문 기록 — 끝까지 코드 반영 · 마지막 아티팩트 검증 보고서
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 21:47:50 +09:00
eomsangdon 5b7fc964a7 Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_2' into main_laptop_1 2026-10-30 21:45:45 +09:00
eomsangdonandClaude Opus 5.5 7ce1841e2e feat(공용 지형): 공용 지형 창고 · 흠 고친 지형 계산 새 모듈 둘 (PLAN 607-1~3 · 요구 486~491)
- common_util_terrain_surface — 방향 무관 굽기(선분 끝 정렬 · 큰 축 칸 중심) · 폐합 안쪽 채움을 그 고리 둘레로 · 보간은 B04 build_laplace 그대로 부름
- common_util_terrain_store — storage/_shared/terrain/<도엽>/dem_<epsg>_<격자>m.npz · 전국 등고선 + 여유 200 m · 도엽 안쪽 칸만 · 열쇠 = 방식 판 + 전국 파일 크기·수정 시각 + 도엽 zip 해시 · 잠금 파일 · 빠진 곳만 도엽 zip(B04 ensure_sheets · prune 안 부름) · load_terrain 진행 % · 단계 글
- 시험 test_607_terrain_store — 뒤집기·조각 굽기 같음 · 봉우리·웅덩이 방향 · 조각 = 통째 · 한 번만 셈 · 원천 바뀜 · zip 길 · 실패 저장 안 함
- B04 · B05 파일 고침 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 21:45:11 +09:00
eomsangdonandClaude Opus 5.5 64b35da265 feat(D 예정노선): 영역 루틴 — 받기 끝 지형면 · 받은 범위 먼저 → 겹 다시(차례 번호) · 도엽 겹 서버 자름 · 위치 칸 제목 서클 하나 · 10 km 밖 알림 (PLAN 604 · 요구 475 · 476-2)
- 받기 끝 = 지형면 · 받은 범위 다 읽은 뒤 겹 판정(동시 읽기 = 옛 범위 판정 뿌리) · 되돌리기 · 다시하기도 영역 확인
- 겹 load(have) — 받은 범위 지문 · 위성 틀 바뀜 = 차례 +1 · 자료 · 받는 중 비움 · 켠 겹 모두 다시 · 옛 차례 답은 자료 · GL 에 안 씀
- 등고 · 세류 = 예정노선 겹 길(layers/sheet) · 도엽 겹 모두 서버가 받은 범위로 자른 사본(화면 상자 · 보관함 지문 걷음)
- 서클 = 위치 칸 제목 오른쪽 하나 · 들어간 일 수(지형 받기 · 지형면 · 겹 · 임도망 올리기) · 부품 상자 원에 제목 줄 꼴(inline) 덧 · 지도 귀퉁이 서클 걷음
- 기준점 10 km 밖 점 = 위치 칸 알림 한 줄
- 시험 553_4 서클 줄 = 새 자리로 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 21:43:20 +09:00
eomsangdonandClaude Opus 5.5 20ed22709c knowledge(요구): 요구 491 원문 기록 — 진행 · B04 · B05 도엽 등고선 바꾸는 곳 조심(B 완료)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 21:33:20 +09:00
eomsangdon 661148719e knowledge(요구): 요구서 486 전국 등고선 공용 지형 · 분석 셋 · 요구 490 원문 — PLAN 607~611 승인 대기 · 606 원천 바꿈
- 등고선 원천 = 전국 등고선 파일 · 도엽 zip = 빠진 곳 · 보조 겹만 · 정션 · 갱신 = 사용자
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 21:30:09 +09:00
eomsangdonandClaude Opus 5.5 50f8d64da2 knowledge(요구): 요구 489 원문 기록 — 미리 계산 때 전국 등고선 vs 도엽 등고선 효과 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 21:23:18 +09:00
eomsangdonandClaude Opus 5.5 68fa3aa68c knowledge(요구): 요구 488 원문 기록 — 배수유역 · LAS 연결 까닭 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 21:21:24 +09:00
eomsangdonandClaude Opus 5.5 3b6571d604 knowledge(요구): 요구 487 원문 기록 — 미리 계산 저장 자리 · E07 전국 등고선으로 미리 분석 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 21:17:44 +09:00
eomsangdonandClaude Opus 5.5 7244050a0a knowledge(요구): 요구 486 원문 기록 — 전국 등고선 쓰기 · 오래 걸리는 지형 분석 미리 해 두기 대상 찾기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 18:39:27 +09:00
eomsangdonandClaude Opus 5.5 5711bd7396 knowledge(요구): 요구 485 원문 기록 — 도엽 vs 전국 등고선 겹쳐 그림 대조 · 아티팩트
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 18:24:40 +09:00
eomsangdonandClaude Opus 5.5 90c76e39b3 knowledge(요구): 요구 484 원문 기록 — 전국 등고선 vs 도엽 같은지 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 18:21:04 +09:00
eomsangdonandClaude Opus 5.5 dcf2a7b656 knowledge(요구): 요구 483 원문 기록 — 등고선 원천(전국 gpkg vs 도엽) 해상도 까닭 확인 요청
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 18:17:46 +09:00
eomsangdonandClaude Opus 5.5 9a9abbdd6b knowledge(요구): 요구 481 · 482 원문 기록 — 초기 30 GB 자료 확인 · 브이월드 수치지형도 축척 고르기 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 18:14:04 +09:00
eomsangdonandClaude Opus 5.5 aa0e7e1b4c knowledge(요구): 요구서 475 · 분석 둘 — 영역 루틴 · 도엽 조각 화면 맞춤 · 올린 임도망 색 · 목록 · 서클 자리 · PLAN 604~606 승인 대기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:08:18 +09:00
eomsangdonandClaude Opus 5.5 96f2dce350 knowledge(요구): 요구 480 원문 기록 — 도엽 원자료 zip 받는 곳 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 18:04:48 +09:00
eomsangdon e023bbd322 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-30 18:01:34 +09:00
eomsangdonandClaude Sonnet 5.5 6b91474ee7 feat(D 예정노선): 위성 바탕 조금 어둡고 연하게 + 범례 「위성 밝기」 조절 칸 · 갈래 선 굵기 3 (PLAN 606-1 · 요구 473 · 475)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 18:00:59 +09:00
eomsangdonandClaude Opus 5.5 92866f24c7 knowledge(요구): 요구 478 · 479 원문 기록 — 도엽 병합 까닭 · 전국 도엽 한 번 받기 · 갱신 · 미리 받기 속도 · 전국 도엽 크기 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015uTVHHDv583hbLQAFeBmd9
2026-10-30 17:54:46 +09:00
eomsangdonandClaude Opus 5.5 387403fe4d knowledge(요구): 요구 477 원문 기록 — 수치지형도 화면 맞춤 갱신 방법 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:43:25 +09:00
eomsangdonandClaude Opus 5.5 fc2637504b knowledge(요구): 요구 476 원문 기록 — 올린 임도망 색 · 목록 고르기 · 지움 · 조용한 받기도 서클 · 서클 자리 = 위치 칸 제목 오른쪽
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:42:14 +09:00
eomsangdonandClaude Opus 5.5 c26119ef82 knowledge(요구): 요구 475 원문 기록 — 위성 대비 가 + 다 · tmp 지우기 허락 반영 · 받은 범위 밖 지점 겹 표기 흠 · 영역 따라 지도 표기 루틴화 제안
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:39:10 +09:00
eomsangdonandClaude Opus 5.5 c38369a5b7 knowledge(요구): 요구 474 원문 기록 — 브라우저 탭 워크트리당 하나
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:28:38 +09:00
eomsangdonandClaude Opus 5.5 8d96d457d1 knowledge(요구): 요구 473 원문 기록 — 위성 위 선 대비 개선 방향 물음 · 브라우저 탭 창마다 자기 워크트리로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:27:33 +09:00
eomsangdonandClaude Opus 5.5 817a2061a2 chore(계획): PLAN 594~603 이관 — 예정노선 화면 개편(위치 칸 · 고르기 · 긋기 · 3D · 지형 받기 · 급경사 모달 · 용화 검증 · 요구 463~469)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:35:49 +09:00
eomsangdon 51ffaec8db Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-30 16:33:03 +09:00
eomsangdonandClaude Sonnet 5.5 934e437c4a feat(D 예정노선): 노선 차례 색 셋 밝게 — 검은 테 위 대비 (PLAN 600-1 · 요구 463-24)
- 갈래 · 보라 · 분홍 차례 색 밝기를 0.35 → 0.41~0.45 로 올림(어두운 위성 바탕 위 색 선 대비)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 16:31:59 +09:00
eomsangdonandClaude Opus 5.5 6df509002f knowledge(요구): 요구 470~472 원문 기록 — sub1 삭제 물음 멈춤 · tmp 는 지워도 됨 · 승인함
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:30:35 +09:00
eomsangdonandClaude Sonnet 5.5 5f90f6c0bb fix(D 예정노선): 첫 열림 지점 칸 [추가] 켬 — 지도 가운데가 처음 생길 때 칸 다시 그림 · Page 줄 수 698 → 647 (받기 지켜보기 · 둘레 상자 → UI_Terrain) (PLAN 597 · 요구 463-7)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 16:27:06 +09:00
eomsangdon 8e8812c56d Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-30 16:23:25 +09:00
eomsangdonandClaude Opus 5.5 09bef8b0ee fix(D 예정노선): 받기에 늘 지금 기준점을 실음 — 저장된 자리로 되돌아와도 「멀리 옮김」 물음 (PLAN 596-2)
- 저장된 자리와 같으면 기준점을 안 실어 서버가 옛 범위와 합치려다 칸 상한으로 거절되던 길
- 들어올 때 범위 · 받기 · 다시 받기 모두 기준점 둘레를 함께 봄 · [저장] 몸 site 는 바뀐 때만

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:22:33 +09:00
eomsangdonandClaude Sonnet 5.5 157688a435 feat(D 예정노선): 지도 노선 선에 검은 테 (PLAN 600-1 · 요구 463-24)
- paintNetwork 선 그리기 = strokeCased(검은 테 + 차례 색 선) · 고름(active) · 흐림(dim) · 갈래 굵기 그대로
- 지킴 시험 551_2 에 paintNetwork 가 strokeCased · routeColor 를 쓰는지 한 줄 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 16:10:18 +09:00
eomsangdon 4f69a9bd67 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-10-30 16:06:13 +09:00
eomsangdonandClaude Opus 5.5 1d2e8f74e9 fix(D 예정노선): 그리기 칸 길이 · 기울기 두 열이 좁은 왼쪽 판에서 넘침 — 칸이 줄어들게 (PLAN 599-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:05:28 +09:00
eomsangdon 0a45253e97 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-30 16:01:41 +09:00
eomsangdonandClaude Sonnet 5.5 f8271e7559 feat(D 예정노선): 검은 테 두껍게 · 줌아웃 한도 지킴 시험 (PLAN 600-1 · 600-3 · 요구 463-24)
- strokeCased 테 +4 px · casing 투명도 0.9 — 밝은 회색 바탕(위성 끔 · 음영 켬)에서 미리보기 대비 3 : 1 이상
- 새 시험 test_600_3_zoom_limit — 휠 하한 인자 기본 0.5 · B05 는 인자 안 줌 · 예정노선만 600 km

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 16:00:43 +09:00
eomsangdonandClaude Opus 5.5 bb791d3c67 refactor(D 예정노선): 갈래 시점 지정 branchFromEnd 걷음 — 단추 걷음(598-3) 뒤 · 닿음 = 긋기 끝에서 저절로 갈래 (PLAN 599 · 요구 463-15)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:31:15 +09:00
eomsangdon ef99ba6806 Merge remote-tracking branch 'origin/dev' into sub_laptop_6 2026-10-30 15:30:13 +09:00
eomsangdonandClaude Opus 5.5 bcc6328a89 feat(D 예정노선): 지도 도움말 줄 — 다시 긋기 · 닿음 = 갈래 · 노드 · 선 · 구역 · 전체 고름 · 우클릭 메뉴 · 마커 그리기 칸 (PLAN 599 · 598 이음)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:30:07 +09:00
eomsangdon 8244d3ed96 Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-10-30 15:29:17 +09:00
eomsangdon 99b47cfe40 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-30 15:29:16 +09:00
eomsangdon 844e7e8e7c Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-30 15:29:15 +09:00
eomsangdonandClaude Opus 5.5 7c5303a79c fix(D 예정노선): 위치 칸 검증 고침 — 빈 알림 · 빈 목록 숨김 · 멀다 물음 글 겹침 · 띠 넘김 맞춤 상자 · 임도망 거절 처리 (PLAN 596-2)
- 찾기 칸 알림 · 후보 줄이 비면 숨김(칸 사이 빈 틈)
- 멀다 물음 = 서버 글 그대로 · 띠 = 「그은 선 · 지점을 지웁니다」 덧
- 다른 TM 띠로 넘을 때 지도 맞춤 상자도 새 띠 좌표로
- 임도망 올리기 거절이 처리 안 된 거절로 새던 길 막음(알림은 그대로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:26:44 +09:00
eomsangdonandClaude Opus 5.5 4415f422f4 Merge remote-tracking branch 'origin/dev' into sub_laptop_5 — 598 합치기 풂(PLAN 598 · 요구 463)
- UI_Points: 우클릭 메뉴 = UI_Menu 그대로 · sub4 597-2 칸 몫([추가] · 펼침 이름 · 경위도 · nameError · 칸 설명 글 걷음) 살림 · 핀 이름 그림(pointLayer names) · dev 쪽 openMenu · mount 걷음(지점 놓기 노드 붙음 = UI_Menu place)
- UI_Pieces: 구역 지움 = edit.dropRegion(line.id)(598 이 dropLine 을 dropRegion 으로 바꿈)
- UI_Page: 지점 한 걸음 stepPoints — 위치(site) 같이 실음(596-2 Snap) · 700 줄
- UI_Edit: 고름 강조 선 = strokeCased 검은 테(600-1 · 밝은 바탕 대비)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:22:21 +09:00
eomsangdonandClaude Opus 5.5 4a1f230b42 chore(D 예정노선): 급경사 다시 칠하는 동안 칠하기 ms = -1 표시(검증 창구 · PLAN 603)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:20:25 +09:00
eomsangdon c6b13098b8 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-30 15:17:24 +09:00
eomsangdon 911fe0c0f1 Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_5 2026-10-30 15:16:39 +09:00
eomsangdonandClaude Sonnet 5.5 da473b041b feat(D 예정노선): 줌아웃 한도 인자 · 느린 조작 시간 표식 (PLAN 600-3 · 600-4 · 요구 463-27)
- 휠 부품 하한 인자 minScale(안 주면 0.5 = B05 그대로) · 예정노선 지도만 화면 폭 약 600 km(한반도)까지 뺌
- performance 표식 perfTime — 지도 다시 그림 · GL 카메라 · 바탕 · 위 그림 · 마우스 움직임(__aisloPerf 켰을 때만)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 15:15:08 +09:00
eomsangdon ad0eb31214 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-10-30 15:14:52 +09:00
eomsangdon fdea248030 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-30 15:14:51 +09:00
eomsangdonandClaude Opus 5.5 edc20df849 feat(D 예정노선): 급경사 범위 옵션 모달 — 기울기 % 값 그림 · 1 % 칸 수 · 화면 칠하기 · 범례 ⚙ (PLAN 603 · 요구 466~468)
- 서버: 15 · 35 · 40° 단계 PNG 길을 걷고 기울기 % 값 그림 한 장(칸 = % 내림 0~250 · 빈 칸 255) + 1 % 칸 수 · 파생 저장본 판 2(열쇠 = 격자 지문)
- 화면: 고른 범위대로 값 그림을 칠함(서버 왕복 없음) · 범례 비율 = 칸 수로 바로 셈 · 범례 급경사 줄 ⚙ → 모달(아래 % · 위 % · 색 · ° 환산 · 범위 추가 · 지움 · 기본값 · 빈 값 · 겹침 막음)
- 기본 = 지식DB 15 · 35 · 40° 의 % 26.8 · 70.0 · 83.9 · 기억 = 화면 취향 aislo:pref:d03.slope-bands(계정)
- 시험: 569_1 급경사 시험을 값 그림 · 칸 수로 고침, 581_1 은 bands 대신 counts(옛 단계 응답이 없어짐) · 새 603 시험(칸 수 합 = 유효 칸 · 기본 비율 = 옛 15 · 35 · 40° 비율)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:12:27 +09:00
eomsangdon 7427a19246 Merge remote-tracking branch 'origin/dev' into sub_laptop_4 — DrawPaint import 정리 2026-10-30 15:12:19 +09:00
eomsangdonandClaude Opus 5.5 ed7f7fa674 feat(D 예정노선): 노드 판 — 방식 · 값 고침(앞 노드 기준 다시 놓음 · 뒤 노드 그대로 · 어긋남 ⚠) · [지점으로] · [다시 긋기] 이음 (PLAN 598-4 · 요구 463-11 · 464)
- logic/Select placeNode — 지점 자리 노드면 지점 같이 · 첫 노드 = 까닭 글
- UI_Props 노드 판 = 599-1 방식 칸(createDrawKindFields) · 방식 고름 = 바로 · 수 칸 = Enter · 칸 떠날 때
- 판 · 메뉴 [다시 긋기] = draw.respan(599-3)
- 시험 598_1 placeNode 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:02:48 +09:00
eomsangdonandClaude Opus 5.5 36750b380f fix(D 예정노선): 받기 꼴 이름 TerrainAim 맞춤 — 595 합침 뒤 typecheck 0 (PLAN 596-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:02:01 +09:00
eomsangdon 50929ef24f Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-10-30 15:00:46 +09:00
eomsangdonandClaude Opus 5.5 bcf397ab3d feat(D 예정노선): 지형 칸 → 위치 칸 · 주소 · 장소 찾기 → 이동 → 받기 · 등록 지도 찾기 (PLAN 596-2 · 596-5 · 요구 463)
- 새 D03_PlanAnalysis_UI_Site — 지금 주소 · 찾기(createFindList · searchPlaces) · 실패 · 끊김 한 줄 + [다시 받기] · [임도망 올리기]
- 위치 = 캐시 한 걸음(Snap site · 판 5 · 되돌리기 · 떠남 물음) → [저장] 몸 site · 지점 이름 point_names
- 고름 → 지도 이동 → 둘레 받기(center) · 멂 · 다른 TM 띠 = 물음 한 번 뒤 reset · 아니오 = 위치 되돌림
- 임도망 올린 뒤 지도 맞춤 = 주소지 둘레 ∪ 잘린 임도 상자(없으면 주소지)
- 조용히 받기 = 기준점 10 km 안 지점 · 선만 · 칸 글 걷음(실패 · 끊김 · 기준점 없음 · 못 불러옴만)
- 등록(D01) 지도 머리 [위성] 오른쪽 찾기 = 같은 createPlaceFind · 사전 DE_Reg_Find*
- 시험 553_4 위치 칸 · 등록 찾기 부품 · 560_4 savedJson 꼴 · 584_3 올리기 자리(위치 칸)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:00:32 +09:00
eomsangdonandClaude Opus 5.5 b44066da61 fix(D 예정노선): 토막 지움이 노드 기록(594-1 nodes)도 같이 자름 — 뒤쪽 첫 기록 = 없음 (PLAN 598-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:59:19 +09:00
eomsangdon 6f7b04d25b Merge remote-tracking branch 'origin/dev' into sub_laptop_4 — Api 는 dev 쪽 · DrawPaint import 둘 다 · Page 구역 목록 인자 + 재기 인자 2026-10-30 14:58:58 +09:00
eomsangdonandClaude Opus 5.5 b2bfc50222 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1
- 충돌 D03_PlanAnalysis_Api.ts: 지형 받기 꼴 = 메인(TerrainAim · aim) 그대로 · D03_PlanAnalysis_UI_DrawPaint.ts: import 둘(nodeOff · pointLabel) 양쪽 살림 · typecheck 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:58:39 +09:00
eomsangdon 2e6aa17648 Merge remote-tracking branch 'origin/dev' into sub_laptop_5
충돌 = UI_Page.ts 메뉴 블록 옆 주석(596-4) — 둘 다 살림
2026-10-30 14:58:32 +09:00
eomsangdonandClaude Sonnet 5.5 10402b3ee1 feat(예정노선): 구역 목록 색 띠 · 이름 칸 · 연도 고름 · 지움 · 측점 글 작게 · 지점 이름 규칙은 Plan.nameError (PLAN 597-2 · 597-3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:57:51 +09:00
eomsangdon 15ae6b98dc Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_5 2026-10-30 14:54:36 +09:00
eomsangdon 3e955e2c85 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_5 2026-10-30 14:52:43 +09:00
eomsangdon 6631604504 Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_5 2026-10-30 14:52:41 +09:00
eomsangdonandClaude Opus 5.5 e68744c3eb fix(D 예정노선): 노드 위 핀 누름 = 노드 고름 · 끌면 지점 같이 (PLAN 598-3)
- 핀이 노드 자리에 있으면 핀 겹이 누름을 안 먹음 — 노드 판 · 넓힘이 가능

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:51:48 +09:00
eomsangdon 5adc1f12c8 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-10-30 14:50:03 +09:00
eomsangdon 0f05b4fde6 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-30 14:50:00 +09:00
eomsangdonandClaude Opus 5.5 7f17f84d4c Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1
- 충돌 D03_PlanAnalysis_UI_Page.ts: 구역 목록 줄(sub4 · 597-1) + 거리재기 노선 인자(sub7 · 600-2) 양쪽 살림 · typecheck 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:49:52 +09:00
eomsangdon 9322f4bde3 Merge remote-tracking branch 'origin/dev' into sub_laptop_4 — Api(595 받기 꼴은 dev 쪽 · 임도 상자 중복 칸 하나로) · Style(597 · 599 절 둘 다) 2026-10-30 14:49:26 +09:00
eomsangdonandClaude Opus 5.5 9febb63e13 feat(D 예정노선): 노드마다 긋기 방식 · 값 기록 — 꼭짓점과 같이 움직임 · 옮긴 노드 기록 남기고 경고 고리 (PLAN 599-2 · 요구 463-11)
- 점 찍을 때 그때 방식 · 값을 nodes 칸에(594-1 꼴 · 붙은 점 = 자유 · 처음 기록 = 앞 노드 기록 없음)
- 끼움 · 더함 · 뺌 · 지움 · 뒤집기(토막 끝 노드 몫 한 칸 밀림) · 잇기 · 다시 긋기(뒤 노드 = 자유)가 기록 길이를 꼭짓점과 맞춤 — [저장] 400 막음
- nodeRecord · nodeOff(길이 1 cm · 기울기 0.1 % 넘게 어긋남) — Span.ts(Plan 700 줄 넘지 않게) · 598 노드 판이 씀
- 시험 599_3 에 기록 따라감 · 어긋남

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:49:21 +09:00
eomsangdonandClaude Opus 5.5 fc43341e4a Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1
- 충돌 D03_PlanAnalysis_Api.ts: 지형 받기 꼴 이름(TerrainCenter · TerrainAim) 같은 뜻 둘 → sub2(595 몫) TerrainAim 쪽 살림 · 쓰는 곳 없음
- PlanForestRoads bounds · distance_m 두 벌 → sub2 한 벌 · typecheck 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:49:10 +09:00
eomsangdonandClaude Opus 5.5 ff49f2c8a2 feat(D 예정노선): 고름 층 노드 → 선 → 구역 → 전체 · 판 하나 재활용 · 우클릭 메뉴 · 지점 ↔ 노드 붙음 (PLAN 598-1~3 · 요구 463)
- 새 logic/D03_PlanAnalysis_Select.ts — 토막 판정 · 끌어 고르기 토막 · 토막 지움(가운데 = 구역 둘 · 뒤쪽 새 구역) · 전체 지움 · 지점 ↔ 노드 같은 자리
- 새 UI_Select.ts — 고름 상태(다시 누름 = 한 단 넓힘 · 같은 해 = 왼쪽 해 줄로만)
- UI_Edit.ts — 우클릭 즉시 지움 걷음 · 노드 끌면 지점 같이 · 지점 12 px 안 붙음 · 지움 창구(dropNode · dropRegion · dropAll · splitAt)
- UI_Props.ts — 판 = 노드 · 선 · 구역 · 전체 · 구간 · 마커 몸 · [방향 바꾸기] · [갈래 시점 지정] 걷음
- 새 UI_Menu.ts — 우클릭 메뉴(Points 에서 옮김 · 노드 지움 · 지점으로 · 연도 구역 나눔)
- UI_Points.ts — 핀 놓을 때 노드 12 px 안 붙음(nodeNear)
- key_registry plan 줄 · 시험 585_2 갈래 시점 지정 줄 지움(요구 463-15 걷음) · 553_4 메뉴 자리 · 새 시험 598_1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:47:39 +09:00
eomsangdon d5ac9f348c Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_4 2026-10-30 14:47:33 +09:00
eomsangdon 9c82825488 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_4 2026-10-30 14:47:29 +09:00
eomsangdon 544c3498cd Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_4 2026-10-30 14:47:28 +09:00
eomsangdon 3a32b51b06 Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_3
# Conflicts:
#	D03_PlanAnalysis/D03_PlanAnalysis_UI_Page.ts
2026-10-30 14:46:42 +09:00
eomsangdonandClaude Sonnet 5.5 0bf234e2b5 feat(예정노선): 지점 칸 [추가] · 이름 · 경위도 손 고침 · 칸 설명 글 걷음 · 선마다 차례 색 (PLAN 597-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:46:22 +09:00
eomsangdonandClaude Opus 5.5 0c463bcc64 feat(D 예정노선): 지형 받기 서버 — 지적 뺌 · 실패 조각만 · 위치 바꿈 멀다 · 띠 · reset · 칸 상한 · 끊김 · 단계 글 · 임도망 ±5 km (PLAN 595 · 요구 463)
- 지적도를 지형 일에서 뺌(겹 GET 이 받음) · 지적 실패 조각만 다시(열쇠 failed_tiles)
- 받기 몸 center · reset — 5 km 넘음 far · 다른 TM 띠 band 물음 · reset = 옛 범위 · 병합 도엽 · 위성 버림
- 받을 칸 500 만 넘음 413 · extent max_cells · lon/lat
- 진행 기록 파일 — 서버 다시 켜짐 = interrupted(percent < 100 · message 없음) · 받은 적 없음 percent 100
- 진행 단계 글 n/5 · 받은 범위에 epsg · center 기록
- 임도망 자르기 = 기준점 ±5 km ∪ 받은 지형 · 지형 전에도 올림 · bounds · distance_m · 밖뿐 = 거리 글
- 시험 530_1 · 530_2 · 569_1 · 584_3 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:45:26 +09:00
eomsangdon b92aae3254 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_3 2026-10-30 14:45:00 +09:00
eomsangdon 5fba814689 Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-10-30 14:44:50 +09:00
eomsangdonandClaude Opus 5.5 acdd0aa469 feat(D 예정노선): 받기 진행 = 지도 귀퉁이 서클 · 끊김 = [다시 받기] · 겹 다시 받기 = 받은 범위 판정 · 보기 옮김 받지 않음 (PLAN 596-3 · 596-4 · 요구 463)
- 부품 progress 원에 corner 꼴(오른쪽 아래 · 글 왼쪽) · 부품 모음 견본
- 서버 다시 켜짐(끝 아닌데 멎음 · 진행 못 읽음) = 끊김 → [다시 받기]
- 겹: 받은 범위(have) 바뀌면 새로 받음 · 등고 · 세류 주소에 범위 지문 &v= · 등고 자르는 상자 = 받은 범위
- 임도망 [올리기] 지도 겹 아이콘 줄에서 왼쪽 칸 단추로(Layers 가 uploadRoads 내줌)
- Api: 받기 center · reset · 답 far · band 꼴 · 진행 interrupted · 임도망 bounds · distance_m(595 꼴)
- 시험 553_4: 막대 대신 귀퉁이 서클 단언

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:44:39 +09:00
eomsangdonandClaude Opus 5.5 3b34301b56 feat(D 예정노선): 다시 긋기 — 고른 구간 앞 노드부터 긋고 원래 뒤 노드에 붙으면 끝 · 다른 구역에 닿아 끝난 본선 = 저절로 갈래 (PLAN 599-3 · 요구 463-12 · 15)
- 새 logic/D03_PlanAnalysis_Span.ts — replaceSpan · respanBlocked(속 점 갈래 막음 · 뒤 갈래 차례 밈 · 마크 길이 비율 · 조각 id 그대로)
- draw.respan(lineId, from, to) — 598 판 [다시 긋기]가 부름 · 속 구간 흐리게(paintNetwork dim) · Enter = 취소 · Backspace = 점 빼기
- 긋기 끝 꼬리가 다른 구역 꺾임점 = 뒤집어 그 점의 갈래(No.0 = 닿은 점)
- 새 시험 599_3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:44:25 +09:00
eomsangdon 08fc54da40 Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-10-30 14:43:15 +09:00
eomsangdon 74371fcd57 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-30 14:43:13 +09:00
eomsangdonandClaude Opus 5.5 52a9fc4784 feat(공용 지도): 주소 · 장소 찾기 길 GET /api/map/search · 선 이름 내보내기 이음 시험 (PLAN 594-2 · 594-4 · 요구 463)
- /api/map/search?q= — VWorld 주소(도로명 → 지번) 먼저 · 장소(POI) 뒤 · 같은 이름 · 주소 줄 하나 · 키는 서버 · 실패 = 빈 목록
- B01_Dashboard_Map.search_place 더함(있는 search_address 그대로)
- 시험 594_4 새로(VWorld 가짜) · 563_2 붙인 선 이름 = 조각 이름표 · zip 폴더 · 좌표표 xlsx 「선」 칸

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:42:38 +09:00
eomsangdonandClaude Opus 5.5 3c098dd8c1 feat(D 예정노선 · B05 3D): 3D 뷰어 여러 벌 덧 · 예정노선 3D = 노선망 전체 표준 횡단 한 번에 (PLAN 601-2 · 601-3 · 요구 463-21)
- B05 뷰어 setCorridors(builds[]) — 벌마다 노면 그룹 · 지형 잘림 = 벌마다 loftDesign 을 한 PSLG 로 합쳐 TIN 한 번 · setCorridor(한 벌) = 옛 길 그대로
- Tin 설계 꼴 outers · roads 칸(있으면 둘레 · 노면 띠 합집합) · 마커 renderRoutes(여러 선)
- D03 3D 모달 = 선 고름 걷음 · 본선 · 갈래 모두 예상 코리도(리핑암 · 산쪽 절토 · 일반 측구 · 계획고 = 지반고) · 갈래 머리 2.0 m 잘라 냄
- test_553_4 3D 단언 새 화면으로(선 고름 없음 · setCorridors)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:42:36 +09:00
eomsangdon 8cc4ca57c7 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-30 14:41:06 +09:00
eomsangdonandClaude Opus 5.5 bf77b70be9 feat(D 예정노선): 저장 꼴에 지점 이름 · 선 이름 · 노드 기록 칸 · [저장] 몸 기준점 (PLAN 594-1 · 594-3 · 요구 463)
- 파일 points [{x, y, name?}] · lines[].name? · lines[].nodes?(꼭짓점 수와 같음 · kind free/length/slope/both · length_m · slope_pct) — 칸 없는 옛 파일 그대로 읽힘
- PUT 몸 point_names · site{lon, lat, address?} — 기준점이 바뀌면 같은 트랜잭션에서 projects.site_* 고침(주소 = 서버 VWorld · 못 찾으면 몸 주소)
- 이름 20 자 · 파일 이름 못 쓰는 글자 막음 — 서버 400(name_error) ↔ TS nameError 짝 · 노드 기록 수 틀림 400
- 선 이름 = 붙인 이름 · 없으면 차례 이름(lineNames ↔ line_names 짝 · 내보내기 조각 이름에 이어짐)
- TS 꼴 NodeKind · NodeRecord · PlanLine.name/nodes · BasePoint.name · ServerNetwork.point_names/site · searchPlaces 틀
- 시험 530_3 이름 거울 · 591_2 왕복 · 400 · 기준점

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:40:34 +09:00
eomsangdonandClaude Sonnet 5.5 7ae441be67 feat(D 예정노선): 거리재기 첫 점이 노선 위면 노선 따라 길이 (PLAN 600-2 · 요구 463-23)
- 새 순수 셈 logic/D03_PlanAnalysis_Measure — 같은 선 누가 차 · 갈래 고리 따라 올라가 만남 · 이어지지 않으면 곧은 거리
- 재기 겹 — 첫 누름 선 8 px · 꺾임점 12 px 안이면 노선 따라 · 굵게 그림 · 글 「노선 따라 L m · 측점 · Δh」
- Page.ts 한 줄(노선 넘김) · 새 시험 test_600_2_plan_measure

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:40:29 +09:00
eomsangdon bb00d9c998 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-10-30 14:40:09 +09:00
eomsangdonandClaude Opus 5.5 b2138e213d feat(D 예정노선): 그리기 칸 방식 칸 함수 · 마디 고름 2 × 2 · 길이 · 기울기 두 열 · 연도 구역 마커 그리기 칸으로 (PLAN 599-1 · 599-4 · 요구 463-10 · 22)
- 새 D03_PlanAnalysis_UI_DrawPanel.ts — createDrawKindFields(노드 판 598-4 가 다시 씀) · 그리기 칸 몸
- 「둘 다」 → 「길이·기울기 고정」 · 칸 설명 글 걷음(요구 463-6)
- 부품 마디 고름에 columns 덧(여러 줄 꼴) — 한 줄 꼴 그대로
- 지도 도구줄 연도 마커 단추 · 공용 표 줄 걷음 → 그리기 칸 끌기 단추
- 시험 553_4 · 568_1 고침(마커 자리 옮김 · 방식 칸 새 파일)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:39:41 +09:00
eomsangdon 69b3aa852f Merge remote-tracking branches 'origin/sub_laptop_3', 'origin/sub_laptop_7' and 'origin/sub_laptop_8' into sub_laptop_4 2026-10-30 14:38:13 +09:00
eomsangdonandClaude Sonnet 5.5 26c5c0343d feat(D 예정노선): 지도 전용 그림 색 · 선마다 차례 색 · 검은 테 (PLAN 600-1 · 요구 463-24)
- 새 값 --draw-map-sketch · measure · warn · route-0~4 (테마 안 따름) · routeColor(index) 차례 색 함수
- planColors 가 지도 전용 색 + casing · 미리보기 · 거리재기 · 마커 · 핀 · 지점 검은 테 (strokeCased)
- 시험 551_2 에 지도 전용 색 대비(어두운 바탕 · 검은 테 ≥ 3 : 1) 덧

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:37:54 +09:00
eomsangdonandClaude Opus 5.5 18fcb059ae feat(D 예정노선): 예상 횡단 되살림 · 노선망 3D 선마다 입력 · 갈래 머리 잘라 냄 (PLAN 601-1 · 요구 463-21)
- logic/D03_PlanAnalysis_Expect.ts 되살림(cdcdd5f59^) — 리핑암 · 산쪽 절토 · 일반 측구 · 계획고 = 지반고
- expectLine · branchTrimM 덧 — 갈래 머리 = 차도 반 + 노견(2.0 m) 잘라 냄 · 측점 = 원래 누가
- test_544_1 되살림 + 노선망 선 시험 하나

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:37:49 +09:00
eomsangdonandClaude Opus 5.5 2aaad7fe32 feat(D 예정노선): 단계 이름 「노선 분석」 → 「예정노선」 — 사전 · 대시보드 단계 · 단축키 표 (PLAN 596-1 · 요구 463)
- 폴더 · 주소 · 열쇠 그대로 · 시험 547_1 단언 새 이름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:37:39 +09:00
eomsangdon ad8506b186 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_5 2026-10-30 14:36:57 +09:00
eomsangdonandClaude Sonnet 5.5 c1350a5b1f refactor(예정노선): 구역 목록을 페이지가 직접 붙임 — 속성 판은 판만 (PLAN 597-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:36:46 +09:00
eomsangdon bc2708487d Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-30 14:36:03 +09:00
eomsangdonandClaude Opus 5.5 37068d439d feat(D 예정노선): Page 지점 넘김 꼴 — 지점 이름 · commit(points, names, network) · 보기 가운데 center (PLAN 596 이음 · 597 받음)
- Snap 에 pointNames · 캐시 판 4
- Points 인자 꼴만 맞춤(이름 함께 넘김) · 칸 고침은 597-2 몫

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:35:12 +09:00
eomsangdonandClaude Sonnet 5.5 f56b5f35f5 refactor(예정노선): 구역 목록을 속성 판 파일에서 UI_Pieces 로 떼어냄 (PLAN 597-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 14:35:10 +09:00
eomsangdonandClaude Opus 5.5 4cb77b2bcd knowledge(요구): 요구 469 원문 기록 — PLAN 594~603 진행
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:27:01 +09:00
eomsangdonandClaude Opus 5.5 0c8e04a48d knowledge(요구): 요구 468 원문 · 요구서 463 6장 — 급경사 기준 값 단위 %
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:23:36 +09:00
eomsangdonandClaude Opus 5.5 3699af2c87 knowledge(요구): 요구 467 원문 · 요구서 463 6장 — 급경사 기준 옵션 모달(띠 추가) · PLAN 603
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:23:09 +09:00
eomsangdonandClaude Opus 5.5 29f6137d65 knowledge(요구): 요구 466 원문 기록 — 격자 2 m 유지 · 급경사 기준 10 · 15 · 20 바꿀 수 있는지 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:20:01 +09:00
eomsangdonandClaude Opus 5.5 1a54395ea9 knowledge(요구): 요구 465 원문 기록 — 격자 = DEM 계산 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:16:39 +09:00
eomsangdonandClaude Opus 5.5 20ae4fc2a8 knowledge(요구): 요구서 463 4장 사용자 답 — 지형 격자 2 m 그대로 · 「지점으로」 = 노드 판 옵션 (요구 464)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:14:55 +09:00
eomsangdonandClaude Opus 5.5 320682a9c0 knowledge(요구): 요구 464 원문 기록 — 지형 격자 유지 · 등고선 간격 물음 · 463-11 「지점으로」 뜻 맞음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:14:17 +09:00
eomsangdonandClaude Opus 5.5 3ccd5d82ee knowledge(요구): 요구서 463 · 분석 셋 — 예정노선 화면 개편(위치 칸 · 고르기 · 긋기 · 3D · 지도 받기) · PLAN 594~602 승인 대기
- 분석 A 왼쪽 판 · 위치 · 지점 / B 그리기 · 고르기 · 3D / C 자료 · 성능 · 임도망
- 사용자 결정 둘(지형 격자 · 「지점으로」 뜻) · 정함(브레인) 스물하나

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:10:49 +09:00
eomsangdonandClaude Opus 5.5 0f20f6472a knowledge(요구): 요구 463 원문 기록 — 예정노선 D03 페이지 개선 27 항목 · 조사 · 요구서 · 계획 요청
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:39:26 +09:00
eomsangdonandClaude Opus 5.5 b6df6bf69a knowledge(요구): 요구 462 원문 기록 — tmp 백업 지움 허락
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:35:44 +09:00
eomsangdonandClaude Opus 5.5 4c9d6952a8 chore(계획): PLAN 590 이관 — 예정노선 설계 = 폴더 파일 · 위성 조각 정리 · 옛 DB 칸 지움(요구 451~461)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:34:32 +09:00
eomsangdonandClaude Opus 5.5 8270ae502d fix(복제): 예정노선 표 원본 차례대로 복사 — SQL 025 가 연도 색인을 지운 뒤 업체 줄 차례가 바뀌어 시험 573_1 실패 · ORDER BY id · year (PLAN 590-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:34:22 +09:00
eomsangdonandClaude Opus 5.5 ff818b21da knowledge(요구): 요구 460 · 461 원문 기록 — DB 칸 지움 허락 · 위성 용량 물음 · 전역 깃 싱크
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:24:47 +09:00
eomsangdonandClaude Opus 5.5 152516bb59 feat(D 예정노선): 옛 DB 설계 칸 읽기 걷음 · 지움 SQL 025(만들기만) — 설계 내용 = 폴더 파일만 (PLAN 590-3 · 요구 458 · 459)
- 옮김 끝 — 파일 없는 PLAN 10 개 = 옛 칸 → plan_network.json 한 번(일회 스크립트 · 11 개 모두 파일 대 DB 대조 같음)
- 저장 모듈 = 파일 · 없으면 빈 설계(옛 칸 · 표 안 읽음) · 옛 연도 노선 접기(legacy_network) · 응답 legacy 칸 걷음
- D04 조각 노선 열쇠 route_json → route(옛 칸 이름과 헷갈림 걷음 · 메모리 값)
- 복제 도구 = 옛 D 표 안 뜸 · 예정노선 설계는 폴더 파일로 따라감
- 024_plan_site_fill.py 지움 — 옛 칸을 읽는 일회 도구 · 기준점 11 개 모두 채움
- SQL 025 = 단계 번호(PLAN_BASE 걷음) · plan_networks · plan_route_pieces · projects.plan_base_json · 연도 줄 노선 칸 · 업체 줄 year 지움 — 공유 DB 적용 안 함(데스크탑 임시 배포가 새 코드를 받은 뒤 · 임시 DB 두 번 돌림 확인)
- 시험: 590_1 옮김 시험 셋 → 파일 없음 = 빈 설계 · 코드에 옛 칸 이름 0 · 530_2 옛 연도 노선 시험 지움(그 길 걷음) · 591_2 옛 지점 꼴 시험 지움 · 591_1 채움 도구 시험 지움(도구 지움) · 573_1 = 파일째 따라감 · 옛 표 목록 밖 허용(025 전) · 531_3 · 563_2 · 593 = 열쇠 이름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:45:53 +09:00
eomsangdonandClaude Opus 5.5 912cfe8263 knowledge(요구): 요구 458 · 459 원문 기록 — 590-3 설명 · 복제 대 옮김 · 진행
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:29:38 +09:00
eomsangdon 4a25f7ae00 Merge remote-tracking branch 'origin/main_desktop_1' into sub_laptop_8 2026-10-30 11:08:52 +09:00
eomsangdonandClaude Opus 5.5 91426dd0d5 knowledge(경쟁사): EST Plus 2 Ghidra 실행 기록 둘 — *.log 무시 규칙에 걸려 빠진 분석 근거(요구 457)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:05:04 +09:00
eomsangdonandClaude Opus 5.5 85a452526a knowledge(경쟁사): EST Plus 2 · STmate REA 분석 자료 올림 — 데스크탑 메인에만 있던 미추적 자료(전역 깃 싱크 전 · 요구 457)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:04:21 +09:00
eomsangdonandClaude Opus 5.5 4038779ae9 chore(git): graphify-out/manifest.json 추적 뺌 — 폐기 도구 결과가 auto: 커밋(f33fa9d92)으로 쓸려 들어옴 · 데스크탑 폴더 받기를 막음(추적 안 된 같은 파일) · .gitignore 에 graphify-out/
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:57:41 +09:00
eomsangdonandClaude Opus 5.5 31da501728 knowledge(요구): 요구 457 원문 기록 — 사용설명서 한 번에 · 전역 깃 싱크 · 데스크탑 네 폴더 누락 없이
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:50:01 +09:00
eomsangdonandClaude Opus 5.5 c26f7ddc46 chore(계획): PLAN 591 이관 — 예정노선 등록 기준점 · D02 걷고 D03 지점 통합(요구 452 · 453)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:42:54 +09:00
eomsangdon 58cf4c1077 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
# Conflicts:
#	resources/tester/test_591_1_plan_site.py
2026-10-30 10:42:06 +09:00
eomsangdonandClaude Opus 5.5 eed1074416 chore(계획): PLAN 593 이관 — 시간 표시 = 보는 사람 PC 시간대 · 옛 로컬 기록 한국 시각(요구 456)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:36:26 +09:00
eomsangdonandClaude Opus 5.5 5bc29bc889 fix(D03): 지점 핀 메뉴 「지점 N에서 긋기 시작」 = 지점 자리에서 · 이어 받기는 지점 · 선이 받은 범위 밖일 때만(옛 프로젝트 여는 즉시 받기 없음) (PLAN 591-2)
- 핀 머리를 우클릭해도 긋기 첫 점 = 지점(전엔 누른 자리 = 지점과 18 px 떨어진 새 본선)
- 받은 범위 안 · 가장자리 여유(350 m) 안쪽 선이 있다고 다시 받지 않음 — 받을 때 서버가 여유를 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:36:15 +09:00
eomsangdonandClaude Sonnet 5.5 bbc61e5040 fix(시간 표시): 서버 시각 모두 끝 Z · Z 없는 옛 기록 = 한국 시각으로 읽음 (PLAN 593-2 · 요구 456)
- 서버: DB 시각 글(DATE_FORMAT) 끝 Z · B03 올린 때 · D04 확정 시각 · 건강 확인 시각 = UTC Z · 개요 최근 변경 T 치환 뺌
- 서버가 만드는 엑셀(E05 면적 계산 시각) = 한국 시각 글(kst_text · Z 없는 옛 글은 이미 한국 시각)
- 화면: Z 없는 글 = 옛 로컬 기록 → +09:00 로 읽음(옛 파일은 고쳐 쓰지 않음)
- 시험: test_593_time(UTC · 서울 · 뉴욕 시간대 · Z 없는 길 0 정적 검사 · D04 · kst_text)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 10:26:24 +09:00
eomsangdonandClaude Opus 5.5 29512c001b feat(D 예정노선): D02 기준 지정 걷음 · 지점 마킹 = D03 노선분석 · 기준점 둘레 1 km 처음 받기 · 조용히 이어 받기 (PLAN 591-2 · 요구 452 · 453)
- D02 걷음 — 길 · 단계(PLAN 단계 둘 = 노선 분석 · 확정) · [>] · 대시보드 단계 점 · 서버 길 · 폴더 만들기 · 캐시 등록 · 글 · 가이드 폴더
- 등록 [등록] · 받은 예정노선 [보기] = D03 으로 곧바로
- D03 지점 = 긋기에 붙는 점 · 목록 칸(여섯 색 · 「지점 N」 · 🗑) · 핀 끌기 = 그 자리 노선 노드 같이 · [저장] 몸에 실음 · [초기화] = 선만 지우고 지점 남김
- 지도 우클릭 메뉴(부품 상자) — 여기서 긋기 시작(빈 땅 = 새 본선) · 갈래 시작 · 이어 긋기 · 지점 놓기 · 지점 지우기 · 긋기 끝
- 받기 — 처음 = 등록 기준점 둘레 ± 1 km(지점 · 선 없을 때) · 지점 · 선 · 지도 보기 가운데가 받은 범위 밖이면 조용히 이어 받음(단추 · 막는 창 걷음 · 실패만 [다시 받기])
- 좌표계 = 파일 머리 · 없으면 기준점 경도 TM 띠
- 시험: 529_3(D02 지점 API) 지움 → 591_2 로 갈음 · 280_2 · 528 · 530_2 · 547_1 · 548 · 553_4 · 556_2 · 560_4 · 401 · 54_2 = D02 단계 · 페이지 걷음에 맞춤 · 590_1 · 530_2_analyze = 사업연도 · 기준점 한 번에 읽음 · 591_1 D04 씨앗 시험 = 590-1 이 씨앗 줄 걷음에 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:19:34 +09:00
eomsangdon 764f7e6c51 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-10-30 10:04:52 +09:00
eomsangdonandClaude Opus 5.5 c58057aff3 test(591-1): D04 연결 설계속도 시험 = 씨앗 함수 걷음 반영(590-1 이 seed_design_speed 지움 · 낡은 부름 → 소스에 design_speed 없음 확인)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:04:36 +09:00
eomsangdonandClaude Sonnet 5.5 59610fecf1 feat(시간 표시): 화면 시각 = 보는 사람 PC 시간대 · 서버 응답 UTC Z · 서버가 만드는 파일 글 = 한국 시각 (PLAN 593-1 · 요구 456)
- 공용 `common_util_time.ts`(날짜 · 시각 · 오늘/어제/n 일 전 · Z 없는 옛 글 = UTC) · `common_util_time.py`(UTC Z 글 · 한국 시각)
- 화면 바꿈: 대시보드(프로젝트 · 휴지통 · 시스템 로그 · 보관함 · 홈 카드) · B09 굳힘 · E03 · E05 · E07 · M02 호표 이력 · 목록 고친 날 · 지난 판
- 서버: 응답 datetime = UTC + Z(main.py) · 로컬 시각 저장 → UTC · 대시보드 경과일 = UTC 기준 · 엑셀 날짜 · 파일 이름 = 한국 시각
- 시험: test_593_time(서울 · 뉴욕 · Z 없는 옛 글 · 서버 글)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 10:03:35 +09:00
eomsangdon 7d71230fca Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-30 09:53:28 +09:00
eomsangdon 24558da7e2 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-30 09:52:51 +09:00
eomsangdonandClaude Opus 5.5 54e08d0d65 feat(D 예정노선): 설계 내용 = 프로젝트 폴더 한 파일 D03_PlanAnalysis/plan_network.json — 저장 모듈 · 서버 길 옮김 · 업체 연도 = 파일 조각 (PLAN 590-1 · 요구 452 · 453)
- 저장 모듈 D03_PlanAnalysis_Store — 읽기 · 원자 쓰기 · 판 · 옛 DB 칸 읽을 때 옮김(지점 셋 꼴 · 연도 행 접기 · 옛 연도 노선) · plan_state(조각 · 연도 지문 · 확정 = 지문 같을 때만)
- D03 [저장] · [초기화] · D02 지점 = 파일만 + 연도 상태 줄(지문 · 확정) · 프로젝트 잠금 · 파일 쓰기 실패 = DB 되돌림 · 빈 연도 줄 지움
- D04 요약 · 업체 지정 · 확정 메일 · 내보내기 · 대시보드 [연결] · 받은 예정노선 · 산림청 홈 · 개요 연결 = 파일에서 · 업체 줄 연도 칸 안 읽음(⑤)
- 산림청 홈 「분석 전」 = 노선 안 그은 예정노선 프로젝트 수 · plan-source 지운 원본 거름
- 옛 연도 단위 지정 · 옛 연도 행 노선 길 걷음(DB 실측 0) · 연결 때 설계속도 씨앗 걷음(591-1 이음)
- SQL 023 = 업체 줄 year NULL 허용(옛 칸은 590-3 에서 지움)
- 시험: 새 지킴 590_1(옮김 · 원자 쓰기 · 연도 읽기 · 잠금) · 낡은 시험 고침 — 529_3 · 530_2 · 531_3 · 531_d04 · 547_2 · 563_2(DB 조각 · 연도 행 흉내 → 파일 흉내 · 옛 연도 지정 시험 · 설계속도 씨앗 시험 지움 = 그 길 걷음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:52:51 +09:00
eomsangdonandClaude Opus 5.5 11569f3d48 test(D01): 591-1 지킴 시험 — D04 연결 빈 설계속도 = 씨앗 안 씀(B05 기본 20) (PLAN 591-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:50:35 +09:00
eomsangdonandClaude Opus 5.5 3d7f6d0b70 feat(D01): 예정노선 등록 — 설계속도 지움 · 카카오 지도 기준점 하나 + 주소(VWorld · 출처 표기) · 사업 지역 채움 (PLAN 591-1 · 요구 452 · 453)
- 등록 화면 = 공용 카카오 지도(마우스 설정 따름 · 일반/위성) 누름 · 끌기로 기준점 하나 · 주소 글 + 출처
- 사업 지역 = 주소의 「시군구 읍면」 채움 · 손으로 고친 값은 안 덮음
- 서버 = 기준점 필수(경위도 범위) · 주소는 서버가 VWorld 로 다시 찾아 저장 · 설계속도 칸 안 씀(B05 기본 20)
- 새 길 GET /api/map/address — 지번 먼저(산 번지) · region · 출처 VWorld · B08 한 줄 주소 그대로
- DB 024 = projects 기준점 칸 셋 추가 · PLAN 설계속도 비움 · 옛 PLAN 기준점 = 지점 가운데(024_plan_site_fill.py · 적용 완료)
- 시험 528 = 설계속도 단언을 기준점 단언으로 · 새 지킴 시험 591_1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:41:31 +09:00
eomsangdonandClaude Opus 5.5 a17ad1dc41 feat(위성): 위성 조각 프로젝트당 150 MB 한도 · 오래 안 본 것부터 지움 · 한 장짜리 위성 걷음 · B 저장 복사에서 조각 뺌 (PLAN 590-2 · 요구 452 · 453)
- 조각 저장본 한 벌(_derived/plan_tile) — 처음 열 때 한 번 셈 · 받을 때 더함 · 넘치면 묵은 것부터 80 % 까지 · 하루 넘게 묵은 것만 본 때 찍음
- 한 장짜리 위성 그림 안 만듦(메타 = 지도 틀만) · vworld-map 위성 404 · 미리 받기 줄 걷음
- 옛 캔버스(D03 · B04 · B05 배수유역) = 위성 바탕 없이 + 알림
- 저장 떠 두기에서 조각 폴더 뺌
- 시험 590_2 새로 · 283 한 본문 손 고침 실패는 고치기 전부터(이 고침과 무관)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:41:00 +09:00
eomsangdonandClaude Opus 5.5 1c0c44f9e2 knowledge(요구): 요구 456 원문 기록 — 시간대 브레인 위임 · 590 · 591 진행
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:33:02 +09:00
eomsangdonandClaude Opus 5.5 6eb9c51d1e chore(계획): PLAN 592 이관 — 지운 프로젝트 휴지통 · 7 일 뒤 영구 삭제(요구 455)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:41:04 +09:00
eomsangdonandClaude Opus 5.5 e13bed9c9c fix(B01): 휴지통 표 — 관리 칸 머리 · 단추 오른쪽 맞춤(세로로 꺾이던 단추) (PLAN 592-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:38:56 +09:00
eomsangdonandClaude Opus 5.5 ac481a0764 feat(B01): 프로젝트 휴지통 — 지움 = 휴지통 · 7 일 뒤 영구 삭제 · 되살리기 (PLAN 592-1 · 요구 454 · 455)
- 대시보드 지움 = 늘 휴지통(deleted_at) · PROJECT_DELETE_HARD_ENABLED 스위치 걷음 → TRASH_RETENTION_DAYS(7)
- 영구 삭제(hard_delete_project) = 휴지통 것만 · FOR UPDATE · 이미 지운 것 False 로 건너뜀 · 감사 기록 동작 이름 받음
- 정리 루프에 purge_expired_trash — GET_LOCK 이름 잠금(두 PC 서버 중 하나만) · 기한 지난 것 PROJECT_PURGE
- API /api/dashboard/trash/projects — 보기 · 되살리기 = 시스템 관리자(전부) · 회사 관리자(자기 회사) · 영구 삭제 = 시스템 관리자만
- 화면 휴지통 — 시스템 관리 › 휴지통 · 회사 관리 › 휴지통(회사 관리자 · 되살리기만) · 지운 사람 = 감사 기록
- 시험 test_592_1_trash 새로 · test_546 메뉴 기대값에 휴지통 · test_573 복제본 지움을 휴지통 거쳐 영구 삭제로(영구 삭제 = 휴지통 것만)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:31:11 +09:00
eomsangdon 109903227d Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_3 2026-10-30 08:20:32 +09:00
eomsangdonandClaude Opus 5.5 9ede651518 knowledge(요구): 요구 455 원문 기록 — 지운 프로젝트 휴지통 · 1 주 뒤 영구 삭제 · PLAN 592
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:20:11 +09:00
eomsangdonandClaude Opus 5.5 80d2141f31 knowledge(요구): 요구 454 원문 기록 — 프로젝트 되살리기 기능 자리 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:18:31 +09:00
eomsangdonandClaude Opus 5.5 992a74e5b0 knowledge(요구): 요구 453 원문 기록 — 452 결정 답 · 기준점 = 처음 자료 기준 · 조용히 이어 받기 · 우클릭 메뉴
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:03:00 +09:00
eomsangdonandClaude Opus 5.5 d12e500baf chore(계획): 요구 452 요구서 · 분석 둘 · PLAN 590 · 591 승인 대기 — 예정노선 저장 자리 · 등록 기준점
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:35:16 +09:00
eomsangdon 35b9f99da8 Merge remote-tracking branch 'origin/main_laptop_1' into CODEX 2026-10-30 07:14:32 +09:00
eomsangdon fc26135aaf Merge remote-tracking branch 'origin/dev' into CODEX 2026-10-30 07:14:27 +09:00
eomsangdonandClaude Opus 5.5 3623d8e35a knowledge(요구): 요구 452 원문 기록 — 예정노선 설계 내용 = 프로젝트 폴더 · 등록 기준점 하나 · 기준지정 페이지 걷고 노선분석으로 통합
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:12:50 +09:00
eomsangdon f33fa9d92a auto: 2026-10-30 06:43 (EOMSANGDON-HOME) 2026-10-30 06:43:25 +09:00
eomsangdonandClaude Opus 5.5 0f293e3e71 knowledge(오솔길): OSKi.arx 정적 분석 최종보고서 · 부록 · 원자료 · 스크립트 추가
- 20_분석/03_ARX정적분석: 최종보고서(범위 · 종단 · 횡단 입력 · 단면 조립 · 면적 분리 · 토적표 C~T · 부대물량 · 측점 처리 · 유토 · 운반 · 엑셀 재계산 · 횡단도 하단 표 · 반올림 · 정적 관찰)
- 부록 A01~A11(Claude 보강) · B(Codex 1차 기록, 정정표 포함)
- 30_원자료/ARX_REA: REA 추출 텍스트 · 소형 JSON · 함수·대화상자 목록 · 엑셀·GPS 조사 자료
- 40_스크립트/ARX_REA: REA 큐 러너 · 큐 작성 · 추출 · 분류 도구
- README: ARX 정적 분석 항목 반영

원본 OSKi.arx 변경 없음(SHA256 일치) · 원본 실행 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 06:43:18 +09:00
eomsangdonandClaude Opus 5.5 50f07f007b chore(계획): 요구 451 분석 — 예정노선 영구 저장 항목 · 눈여겨볼 점
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 06:28:06 +09:00
eomsangdonandClaude Opus 5.5 09f5a9aff3 knowledge(요구): 요구 451 원문 기록 — 예정노선 영구 저장 항목 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 06:16:35 +09:00
eomsangdonandClaude Sonnet 5.5 93f2feed08 test(옛 실패 셋): 551-2 · 346-3 · 449 낡은 단언 고침 (요구 450-2)
- test_551_2: --draw-* 개수 고정(21) 걷음 — 밝음이 전부 정의 · 어두움/시스템 어두움은 밝음 안의 것만 덮음 · 캔버스가 읽는 이름은 세 모드 모두 풀림 (지도 겹 · 차트 색이 늘어 51)
- test_346_3_route_save_screen: [저장] 본문의 route/carry 줄 단언을 정규식으로 — alignment_cleared 칸이 덧붙음
- test_449 헬퍼: 자체 풀이 함수 걷고 공용 불러오기 기본 풀이로 — @ui 로케일 · .css import 를 못 찾아 시작 못 했음

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 05:45:35 +09:00
eomsangdonandClaude Opus 5.5 0994ee7124 knowledge(요구): 요구 450 원문 기록 — tmp 시험 백업 정리 · 옛 실패 시험 셋 개선
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 05:37:14 +09:00
eomsangdonandClaude Opus 5.5 77c15d0396 chore(계획): PLAN 589 이관 — B04 · B05 지도 겹 맞춤 · MapLibre 바탕(요구 447 · 448)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:49:19 +09:00
eomsangdonandClaude Opus 5.5 21afc379e4 feat(B04·B05): 하단 지도 · 평면 노선 편집 · 배수유역도 바탕 MapLibre 적용 — 공용 GL 부품 · TM 주인 카메라 · 처음 = 켬 · WebGL 못 쓰면 옛 캔버스 (PLAN 589-2 · 요구 448)
- 공용 부품 ui_template_gl_map(지도 세우기 · 자료 · 켬끔 · 색 다시 · 카메라) — D03 GL 껍데기를 올림 · D03 은 겹 꼴만(바뀜 없음)
- TM 주인 따라가기(B04_PreProcess_UI_MapGl · logic/_MapGl_Frame) — 화면 가운데 1차 맞춤 닮음꼴 카메라 + 남는 늘림(웹 메르카토르 가로세로 0.43 %)은 지도 칸 CSS 행렬 · 칸 둘레 여유 8 px
- B04 하단 지도 — 위성 조각(줌인 선명) · 백지도 · 혼합 · 도엽 · 지적 · 행정 · 등고선 겹 GL · 등고 글 · 배수유역 · 계획선 · 관 = 위 캔버스
- B05 배수유역도 — 위성 · 세부유역 채움(짝홀 다각형) · 분수령 · 도엽 GL · 번호 · 상류 세류 · 관 · 강도 = 위 캔버스
- B05 평면 노선 편집 — 바탕칠 · LAS · 도엽 등고선(간격 거름 · 1 m 선) · 하천(LAS 상자 자름) GL · 노선 둘레 띠 = 지도 칸 clip-path · 고른 등고선 · 글 · 노선 · 꼭짓점 = 위 캔버스
- 켬끔 칸(GPU · 세 화면 한 취향 aislo:pref:map.canvas) · WebGL 못 씀 · 잃음 = 옛 캔버스 + 알림 · 누름 · 끌기 판정 손 안 댐
- B04 층 단추 글 = MapLayers gisLayerLabel(지도 본체 700 줄)
- 지킴 시험 test_589_2_gl_frame(카메라 대 캔버스 0.5 px · 칸 행렬 늘이기만 · 되돌림 1 mm · 상자 자르기 · 짝홀 다각형)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:45:41 +09:00
eomsangdon 49255c07cb Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-29 21:03:59 +09:00
eomsangdonandClaude Opus 5.5 9c2b5e151b fix(B04·B05): 위성 메타 TM 격자를 상자 둘레 한 폭씩 넓힘 — 25×25 (PLAN 589-1)
- 도엽이 위성 상자 밖 0.8 폭까지 나가 9×9 격자 늘려 씀 자리에서 0.33 m 어긋남(화면 실측)
- 격자 범위 = 상자 ± 한 폭(3 × 3 폭) · 상자 안 칸 크기 그대로(8 등분) · 격자 자기 경위도 범위 칸 더함
- 지킴 시험 = 상자 밖 0.9 폭까지 pyproj 0.02 m 안

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:54:08 +09:00
eomsangdonandClaude Opus 5.5 f488bd399a chore(계획): PLAN 582 · 586 이관 — D03 등고선 성능 · 바탕 MapLibre 전체 적용(요구 440-9 · 445 B · 448)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:32:20 +09:00
eomsangdon d63aae0e9c Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-29 20:31:54 +09:00
eomsangdon a9390ca6e0 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-29 20:31:52 +09:00
eomsangdon 0326c7c33d Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-29 20:31:09 +09:00
eomsangdonandClaude Opus 5.5 8793b4f6b2 feat(D03): 노선분석 바탕 MapLibre 전체 적용 — 겹 스물 · 등고 글 · 지명 GL · 처음 = 켬 · WebGL 못 쓰면 옛 캔버스 (PLAN 586-2 ①②③⑤ · 요구 448)
- GL 겹 스물: 위성 조각 · 음영 · 급경사 · 산사태위험(image) · 산림입지 · 보호구역 · 띠 · 물 면 · 건물(fill + 테두리)
  · 지적 · 등고선 · 도로 · 임도 짐작 · 올린 임도망도 · 세류 · 단층 · 지질구조선 · 0차곡 · 산사태취약 필지(line · 점선 px 그대로)
  · 산사태취약 점 · 지명 점(circle) — 색 · 굵기 · 투명도 · 차례 = 옛 캔버스 그대로
- 음영 = 위성 위 「곱하기 0.75」와 같은 셈의 검은 반투명 그림(밝기 g → 투명도 0.75·(1−g)) · 위성 끔 = 그대로
- 등고 수치 · 지명 = symbol(겹치면 뺌 · 등고 글은 선 따라) · 글꼴 = 화면 Noto Sans KR(OFL · 자체 보관)을 그 자리에서 그림
  (꼴에 글꼴 주소 없음 = 글꼴 조각 파일 없음 · 받기 전에 그 글자 글꼴 미리 받음)
- 카메라 주인 = D03 화면 view · 돌림 하나 — 휠 · 가운데 끌기 · 돌림 · 맞춤 · 단축키 모두 draw() → glCamera
- 처음 = GL(aislo:pref:d03.canvas = "1" 이면 옛 캔버스) · 겹 단추줄 끝 단추로 바꿈 · WebGL 못 열거나 잃음 = 저절로 옛 캔버스 + 알림
- GL 이면 캔버스 바탕 겹은 쉼(등고 주곡선 간격 셈만) · GL 이면 위성 한 장 그림 안 받음
- 산사태위험 그림도 경위도 귀퉁이(lonlat_corners 한 곳) · 묶음 꼴 표시 없는 합친 자료도 GL 이 받게 다시 쌈
- 캔버스 그리기 목록 = 새 D03_PlanAnalysis_UI_LayerPaint.ts(700줄 규칙) · 검증 창구 layers().gl = GL 겹별 그린 지물 수
- 잼(headless Edge 실 GPU · 검증_sub2_D · 겹 18 켬 = 자료 있는 겹 전부): 줌아웃 화면 간격 중앙 58.4 → 4.2 ms · 끊김 27 → 0/30
  · 끌기 62.5 → 4.2 ms · 끊김 37 → 0/40 · 마우스 4.2 ms 끊김 0 그대로 · 그은 선 대 GL 0.019 px(돌림 30° 0.019 · 확대 0.077)
- 시험: 캔버스 그리기를 옮긴 자리로 지킴 시험 넷 고침(581-2 · 582 · 584-1 · 584-3 — 그리기 차례 · 색 값이 LayerPaint 로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:28:13 +09:00
eomsangdonandClaude Opus 5.5 ba18b0f96d fix(B04·B05): 지도 경위도 겹 · 위성 그림을 TM 겹 자리로 맞춤 (PLAN 589-1 · 요구 447 · 448)
- 서버 위성 메타 응답에 그림 상자 9×9 TM 격자(tm_grid · pyproj · 응답 때 셈 · 귀퉁이 1 cm 넘게 다르면 안 줌)
- 공용 createTmNormalizer(고를 칸 toNorm · 격자 쌍선형) · normOfLonLat · imageScreenMatrix · placeMapImage
- B04 하단 지도 · B05 배수유역도 · B05 평면 노선 편집이 TM 자리로 · 위성 그림 = 화면 가운데 1차 행렬(contain 뺌)
- Watershed 손 베낌 식 넷 · 줌아웃 한계 = 공용 식 · D03 길(createNormalizer) 바뀜 없음 · TM 길 손 안 댐
- 지킴 시험 test_589_tm_grid(Bessel · GRS80 · 격자 = pyproj 0.02 m · 그림 행렬 0.5 px · 옛 꼴 어긋남)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:21:39 +09:00
eomsangdon 7beca06a06 Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_2' into sub_laptop_3 2026-10-29 20:13:28 +09:00
eomsangdonandClaude Opus 5.5 41392b7e1c chore(계획): 요구 447 분석 — B04 · B05 지도 겹 어긋남(수렴각 회전) · PLAN 589-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:13:20 +09:00
eomsangdonandClaude Opus 5.5 b08f78cf8e knowledge(요구): 요구 449 원문 기록 — GPU 물음 · 지금 계획 유지(지도 = 589 · 나머지 측정 안 함)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:12:32 +09:00
eomsangdonandClaude Opus 5.5 99db748a7b feat(D03): 노선분석 위성 조각 서버 기억 — 받은 조각 _derived/plan_tile 에 두고 다시 안 받음 (PLAN 586-2 ④ · 요구 448)
- GET …/plan/tile/{z}/{x}/{y} = 프로젝트 _derived/plan_tile/Satellite/{z}/{x}_{y}.jpeg 먼저 · 없으면 브이월드에서 받아 원자 쓰기
- 프로젝트 실경로 기억(조각마다 DB 안 부름)
- 시험: test_586_tm_frame 두 번 받기 = 브이월드 한 번 · 반쯤 쓴 조각 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:03:35 +09:00
eomsangdonandClaude Opus 5.5 39c71e8d27 knowledge(요구): 요구 448 원문 기록 — 어긋남 조사 · MapLibre 전체 적용 · 동시에
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:57:03 +09:00
eomsangdonandClaude Opus 5.5 2ab00c9130 knowledge(요구): 요구 447 원문 기록 — B04 · B05 지도 어긋남 정밀 조사 · MapLibre 물음 · 가운데만 맞는 까닭 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:51:51 +09:00
eomsangdonandClaude Opus 5.5 e64f9deb0e chore(계획): PLAN 588 이관 — 단축키 묶음 · 찾기 · 키 바꾸기 · 팬 단추(카카오 포함)(요구 446)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:44:21 +09:00
eomsangdon 2c5fbb8ef8 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-29 19:38:24 +09:00
eomsangdonandClaude Opus 5.5 27be58d2c8 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 — 586-1 GL 바탕 시험 · test_582 같은 줄 양쪽 같음(주석 살림)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:38:23 +09:00
eomsangdonandClaude Sonnet 5.5 104160edcd feat(설정 마우스): 카카오 공용 지도도 화면 옮기기 단추를 따름 — 가운데 늘 · 오른쪽 고르면 오른쪽도 (PLAN 588-5 · 요구 445 C3)
- 옮기기 단추 = 설정(isAuxPanButton) · 끌기 중 놓침 = buttonMask · 오른쪽 끌기 뒤 메뉴 안 뜸
- 왼쪽 끌기 · 표지 누름 · 손가락 = 카카오 제 것 그대로 · 휠 방향 그대로
- 시험: 설정 오른쪽 · 가운데 늘 · 왼쪽 안 가로챔 대조

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 19:29:11 +09:00
eomsangdonandClaude Opus 5.5 20d248e535 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 — 588 단축키 등록부 · UI_Edit 충돌(sub1 고르기 · 속성 판 + sub4 keyIs) 양쪽 살림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:24:37 +09:00
eomsangdonandClaude Opus 5.5 2998785426 fix(설정 단축키): 묶음 줄 수 배지 = 회색 알약 (PLAN 588-1)
- 빨강 수 배지가 잘못 표시처럼 읽혀 중립 알약으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:22:32 +09:00
eomsangdonandClaude Opus 5.5 d2412297c7 feat(D03): 노선분석 GL 바탕 시험 — MapLibre 바탕 겹 여섯 · 위 캔버스 그대로 · 처음 = 끔 (PLAN 586-1 · 요구 445 B)
- 겹 단추줄 끝 [GL 바탕 시험] 켬끔(aislo:pref:d03.gl · 처음 = 지금 캔버스)
- 켬 = MapLibre(설치본 5.24 · BSD-3)가 위성 조각 · 급경사 그림 · 지적 · 등고선(주곡선 · 계곡선) · 도로 · 세류를 그림
  · 받는 GeoJSON 그대로 · 등고 글 · 그 밖 겹 · 그은 선 · 재기 · 누름 = 캔버스 그대로
- 카메라 = D03 화면(가운데 · 확대 · 돌림)을 따라감 · 같은 화면에 바로 그림 · 캔버스 겹은 MapLibre 투영으로
- 위성 조각 대리 GET …/plan/tile/{z}/{x}/{y}(브이월드 Satellite z6~19 · 키 안 샘)
- 잼(headless 실 GPU · 검증_sub2_D · 겹 여섯): 줌아웃 화면 간격 중앙 20.9 → 4.2 ms · p90 29.2 → 12.5 · 끊김 5 → 0/30
  · 끌기 20.8 → 4.2 ms · 끊김 2 → 0/40 · 그은 선 대 GL 바탕 0.02 px(돌림 30° 같음) · 재기 차 0 · 긋기 점 두 판 차 0.00001 m
- 줌인 = 위성 z19 조각으로 선명 · 급경사 그림 매끈 · 선 자리 두 판 같음
- 시험: test_586_tm_frame 조각 대리 범위 밖 400 · test_582 겹 수 낡은 셈(12 → 584 뒤 17) 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:10:37 +09:00
eomsangdonandClaude Opus 5.5 4a55ee62c1 fix(CAD 빌드): 단축키 등록부를 CAD tsconfig 목록에 더함 (PLAN 588-3)
- tsc -b 가 A00_Common/key_registry.ts 를 목록 밖이라 막던 것

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:09:44 +09:00
eomsangdonandClaude Opus 5.5 883a098cd6 feat(설정 마우스): 화면 옮기기 버튼 고르기 — 왼 · 가운데 · 오른 · 지도 · CAD · 종단 · 그림 칸 모두 따름 (PLAN 588-4 · 요구 445 C3)
- ☰ [설정] 마우스 탭 = 휠 방향 + 옮기기 버튼 라디오 · 규칙 글 = 고른 버튼 끌기 옮김(가운데는 늘)
- 끌면 옮김 · 덜 끌고 떼면 그 버튼 제 몫(고름 · 메뉴 · CAD Enter) · 오른쪽 끌기 뒤 메뉴 안 뜸
- 왼쪽 옮김은 페이지가 잡은 누름(B05 노드 · 손잡이 · 마커 · 재기 · D03 겹 · 그림 안 도형)에선 비킴
- 자리: B05 · B06 · D03 노선 지도 · CAD · 공용 SVG 확대 · B04 지도 · 3D(오른쪽만) · B05 배수 · 종단 가로 · B08 이정표 · M02 흐름도 · 노임
- 시험: CAD 오른쪽 끌기 옮김 · 옮기기 자리 배선 대조 · 낡은 헬퍼 둘 새 import 대응

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:08:40 +09:00
eomsangdonandClaude Opus 5.5 e70cca0814 chore(계획): PLAN 585 이관 — 예정노선 지점 하나 · 고르기 · 끌어 고르기 · 연도 구역 마커(요구 446)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:04:59 +09:00
eomsangdon 480e8c291c Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-29 19:03:54 +09:00
eomsangdonandClaude Opus 5.5 c3f7261058 fix(D03): 고름 맞춤(sync)이 페이지 다시 그림을 불러 돌고 도는 것 막음 — 속성 판이 안 뜨던 흠 (PLAN 585-2)
- refresh → edit.sync → onSelect → refresh 되풀이로 속성 판 그림이 끊김 · sync 는 고름만 맞추고 다시 그림은 부르는 쪽(refresh)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:02:51 +09:00
eomsangdonandClaude Opus 5.5 cc909d107e feat(D03): 연도 구역 마커 끌어 놓기 · 선 밖으로 끌어내 지움 · 긋는 해 걷음 · 갈래 시점 지정 (PLAN 585-3 · 요구 445 A6 · 446)
- 도구줄 「연도 마커」(공용 지도 도구줄 year-mark · 깃발 아이콘)를 선 위로 끌어 놓음 = 그 자리 구간을 둘로 — 구역만 나눔 · 해 그대로
- 끄는 동안 마름모 미리보기 · 선 끝 · 다른 마커 1 m 안 · 선 밖 = 경고 색 · 못 놓음
- 놓인 마커 끌기 = 경계 옮김 · 선 밖으로 끌어냄 · 오른쪽 누름 · Delete = 지움(앞 구간과 합침) · 같은 해 양쪽 = 속 빈 마름모
- 좌측 [연도 구분] · 「긋는 해」 걷음 · 새 선 = 사업연도 시작 해 · 새 갈래 = 붙은 자리 구간 해
- 속성 판 [갈래 시점 지정] = 다른 선에 닿은 끝을 시점(No.0)으로 갈래 · 꼬리에 닿은 머리 = 한 선 · 본선 = [방향 바꾸기]
- 시험: 585-2 시험에 나눔 · 합침 · 갈래 해 · 갈래 시점 덧 · 530-3 갈래 해 · 553-4 속성 판 · 568-1 도구줄 목록 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:51:04 +09:00
eomsangdonandClaude Opus 5.5 2e4316567c feat(설정 단축키): CAD 리본 묶음 · 찾기 · 다른 곳 = 실제 동작 등록부 표 · 키 바꾸기 · 겹침 막음 (PLAN 588-1~3 · 요구 445 C2 · C3)
- 새 key_registry.ts — 화면마다 받는 키 · 마우스 동작 한 벌 · keyIs(evt, id) · 바꾼 키만 input-prefs.keys
- ☰ [설정] CAD 탭 = 찾기 칸 + 「키 · 마우스」 + 리본 탭 › 패널 접이 묶음(기타 여섯) · 날 input → 부품 입력
- 다른 곳 탭 = 손글 안내 지우고 등록부 표(B05 방향키 · D03 Delete 등 빠진 것 포함) · 찾기
- 키 기록 칸 · 줄마다 [기본값] · [모두 기본값] · 같은 화면 · 모든 화면 겹침 · 브라우저 몫 · CAD 글자 단독은 저장 막음
- 배선: B05 되돌림 · 자유지정 Enter · Backspace · D03 되돌림 · 그리기 · 지움(한글 자판도 자판 자리) · CAD F3 · F7 · F8 · F10 · Ctrl 명령 · Delete
- 낡은 글 대조 시험 셋(215-1 · 345-3 · 346-4)은 새 배선 글로 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:50:41 +09:00
eomsangdon 8d92653558 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-29 18:46:50 +09:00
eomsangdonandClaude Opus 5.5 c30fa9a877 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 — 586-0 겹 맞춤 · UI_Page 충돌(points + epsg) 양쪽 살림 · test_582 겹 수 낡은 단언(12 → 모두 바탕)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:46:48 +09:00
eomsangdonandClaude Opus 5.5 e6951b5c73 feat(D03): 예정노선 구간 고르기 넓힘 · 속성 판 · 끌어 고르기 창/걸침 (PLAN 585-2 · 요구 445 A4 · A5)
- 고름 = 구간 묶음 + 넓이 — 선 누름 = 구간 · 다시 누름 = 선 → 같은 해 → 구간 · Shift+누름 = 더함 · 뺌
- 빈 곳 끌기 = 오른쪽으로 창(실선 · 다 든 구간) · 왼쪽으로 걸침(점선 · 걸친 구간까지) · 돌린 지도도 보이는 화면 기준
- 지도 위 속성 판(새 _UI_Props · 공용 떠 있는 판) — 넓이 · 연도(여러 구간 한 번에) · 구간 줄 · 방향 바꾸기 · 선 지움 · 마크 지움
- 좌측 구역 · 연도 = 해 줄 · 선 줄 · 구간 줄 누름 = 고르기(해 고침은 속성 판 한 곳) · 고른 선 위 끌기 = 꺾임점 더해 옮김
- Delete = 고른 마크 · 선 통째 고름이면 그 선 지움
- 시험: 585-2 새 지킴 시험(창 · 걸침 · 돌린 화면 · 여러 구간 연도)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:46:01 +09:00
eomsangdonandClaude Opus 5.5 08864a1108 fix(D03): 노선분석 TM 겹을 경위도 바탕에 맞춤 — 그은 선 · 재기 · 급경사 · 음영 그림 자리 (PLAN 586-0 · 요구 445 B0)
- 까닭: 위성 · 도엽 겹(경위도)과 TM 겹을 같은 직사각형으로 폄 → 자오선 수렴각만큼 어긋남
  (검증_sub2_D 화면 실측: 위성 귀퉁이 58.9 px · 63.6 m · 급경사 그림 귀퉁이 17.7~21.1 px · 19~23 m)
- 고침: TM ↔ 경위도 TS(GRS80 TM 5185~5188 · 5179 · pyproj 짝) + 화면 가운데 1차 맞춤 틀
  → 그은 선 · 재기 · 누름 = 경위도 바탕 자리 · 그림 겹 = 세 귀퉁이로 놓음(돌아 있는 TM 격자)
- 고친 뒤: 급경사 귀퉁이 0.07 px · 위성 가운데 0.02 px · 위성 귀퉁이(화면 밖) 0.6 px
- 모르는 좌표계 · 가짜 도엽 = 옛 꼴 그대로 · 검증 창구 screenOfLonLat 덧
- 시험: test_586_tm_frame(TM 거울 1 mm · 틀 0.5 px · 되돌림 1 mm · 옛 꼴 어긋남)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:44:10 +09:00
eomsangdonandClaude Opus 5.5 9b133443c3 chore(계획): PLAN 587 이관 — B05 교각점 정보 = 근처 아이콘(요구 446)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:43:44 +09:00
eomsangdon b5c49879e9 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-29 18:43:31 +09:00
eomsangdon 248ce44748 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-29 18:42:58 +09:00
eomsangdonandClaude Sonnet 5.5 6aa0582495 feat(B05): 교각점 정보 = 근처 아이콘 · 아이콘 누름 = 정보 창 (PLAN 587-1 · 요구 445 C1)
- 교각점 고름 = 창 없이 근처(40 px) 아이콘 · 멀면 0.7 s 뒤 숨김 · 누름 = 창 열림(멀어져도 유지)
- 창 열린 채 다른 교각점 = 열린 채 새 정보 · 닫힌 채 = 닫힌 채 · [닫기] = 창 닫고 고름 풂
- 부품 상자 아이콘 단추 「유리」 꼴 더함(견본 · 시험 552-2)
- 새 지킴 시험 test_445_curve_icon · 398-2 · 408 도우미에 가짜 아이콘

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 18:42:16 +09:00
eomsangdonandClaude Opus 5.5 f5645c256d feat(D02·D03): 예정노선 지점 하나 · 차례 색 · 노선분석 핀 · 카카오 지도 마우스 설정 따름 (PLAN 585-1 · 요구 445 A1~A3)
- 기준지정 시점 · 종점 목록 → 「지점 목록」 하나 · [+ 지점] · 지점마다 차례 색 여섯 돌림(줄 왼 띠 · 지도 이름표 바탕)
- 저장 꼴 plan_base_json {epsg, points} · 옛 starts + ends · start · end 는 읽을 때 이어 붙임(옮김 없음) · 상한 40
- 노선분석 지점 = 핀 + 「지점 N」 · 기준지정과 같은 색 · 겹 목록 맨 끝(늘 맨 위) · 지점 누름 = 새 본선(No.0 = 그 지점) · 선 놓인 지점 = 갈래
- 공용 카카오 지도 = 휠 설정 방향 한 단(마우스 자리 고정) · 가운데 끌기 옮김 — 기준지정 · 지형 범위 창 · B08 이정표 같이
- 시험: 529-3 · 530-2 새 꼴 · 585-1 새 지킴 시험 · 낡은 B08 가짜 카카오(바탕 종류 · 사건 받기 없음) 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:40:49 +09:00
eomsangdonandClaude Opus 5.5 efa382ae06 feat(설정 마우스): 화면 옮기기 단추 · 바꾼 키 칸 읽기 — panButton · isPanButton · buttonMask (PLAN 588-4 이음 · 요구 445 C3)
- input-prefs 에 panButton(왼 · 가운데 · 오른 · 기본 가운데) · keys(바꾼 키만) 칸
- isPanButton = 고른 단추 또는 가운데 · 585-1 카카오 부품이 부름
- test_input_prefs 기본값 · 단추 판정 · 깨진 값 거름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:36:16 +09:00
eomsangdonandClaude Opus 5.5 808e212a16 knowledge(요구): 요구 446 원문 기록 — 요구서 445 결정 여덟
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:29:26 +09:00
eomsangdonandClaude Opus 5.5 68fd17ab35 chore(계획): 요구 445 요구서 · 분석 셋 · PLAN 585~588 승인 대기 — 예정노선 고르기 · 지도 성능 · 교각점 아이콘 · 단축키
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:58:11 +09:00
eomsangdonandClaude Opus 5.5 5332fd2add knowledge(요구): 요구 445 원문 기록 — 예정노선 선택 · 연도 · 성능 · B05 교각점 아이콘 · 단축키 · 마우스 제어
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:36:27 +09:00
eomsangdonandClaude Opus 5.5 dc112465a6 chore(계획): PLAN 584 이관 — D03 GIS 겹 더함 2(단층 · 0차곡 · 산사태위험지도 · 임도망도 · 산사태취약지역)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:29:29 +09:00
eomsangdonandClaude Opus 5.5 5db9cb49f1 feat(D03 겹): 노선분석 지도 올린 임도망도 · 산사태취약지역 겹 — 시군구 SHP 올리기 · 시도 표 점 + 지번 필지 테두리 (PLAN 584-3 · 요구 443)
- 임도망도: 지도 겹 줄에 올리기 단추(zip 하나 · shp · shx · dbf · prj 묶음 · 100 MB · 넘으면 413) → 서버가 읽어 받은 범위로 자름(prj 없으면 EPSG:5179 · cpg 없으면 cp949) · 원본 = 프로젝트 D03_PlanAnalysis/임도망도.zip · 자른 선 = 파생 저장본 · 못 읽거나 범위 안 선 0 = 400 · 옛 것 그대로
- 범례: 「임도 짐작」(주황 점선) · 「올린 임도망도」(진한 주황 실선) 구분 · 짐작 겹 그대로
- 산사태취약지역: data.go.kr 시도 표 넷(경북 · 충남 · 광주 · 경남)을 resources/data_landslide_zones 에 한 벌(머리 = 이름 · 받은 날 · 기준일 · 출처) · 시도마다 다른 열 이름은 별칭으로 · 위경도 = 받은 범위 안 점 · 지번(시군구 · 읍면동 · 리 · 산 · 본번 · 부번) = 지적 필지 테두리만 · 사업지 시도에 표 없으면 범례 「이 시도 자료 없음」 · 지적 못 받으면 점만
- 두 겹 처음 끔 · 바탕 캔버스 · 색 --draw-map-forest-road-file · landslide-zone · 아이콘 alertOctagon · 고르기 창 pickFiles(부품 상자 ui_template_drop)
- 범례 · 겹별 수 · 받은 자료 꼴을 D03_PlanAnalysis_UI_LayerLegend.ts 로 옮김(700줄 한도)
- test_553_4: 겹 목록에 forestRoadFile · landslideZones 더함(새 겹 · 기대 목록 갱신)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:27:45 +09:00
eomsangdonandClaude Opus 5.5 9abd5deb46 chore(계획): 요구 443 분석 — 임도망도 · 산사태취약지역 출처 · PLAN 584-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:04:46 +09:00
eomsangdon 1cb9a8cb76 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-10-29 17:01:51 +09:00
eomsangdonandClaude Opus 5.5 84f243cee1 feat(D03 겹): 노선분석 지도 산사태위험지도 겹 — 행안부 생활안전지도 WMS · 키 없으면 그 겹만 꺼짐 (PLAN 584-1 · 요구 444)
- 서버: IF_0046_WMS 겹 A2SM_SANSATAI 를 받은 범위 경위도 상자로 한 장(2 m/px · 한 변 2,048) → 작업 좌표계 상자로 옮겨 그림 · 덮으면 저장본 · 키 없음 = 503 「키 없음」 · 받기 실패 · 그림 아님 = 502
- 키 = .env SAFEMAP_API_KEY 새 칸(값 비움)
- 화면: 단추 하나(alert) · 묶음 셋째 · 처음 끔 · 바탕 캔버스 · 범례 1~5 등급(--draw-map-landslide-1~5)
- 켬끔 취향 읽기를 LayerDefs 로 옮김(700줄 한도)
- test_553_4: 겹 목록에 landslide 더함(새 겹 · 기대 목록 갱신)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:59:11 +09:00
eomsangdonandClaude Opus 5.5 1bc7d71f56 knowledge(요구): 요구 444 원문 기록 — 행안부 산사태위험지도도 넣음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:55:28 +09:00
eomsangdonandClaude Opus 5.5 7d4a94a887 feat(D03 겹): 노선분석 지도 단층 · 지질구조선 · 0차곡 겹 — 묶음 셋째 · 처음 끔 (PLAN 584-1 · 요구 443)
- 서버: 선 겹 = 브이월드 WFS lt_l_gimsfault · lt_l_gimslinea 받은 범위로 잘라 가닥 · 길이 · 덮으면 다시 안 받음 · 실패 = 502 · 새 선 겹은 LINE_KINDS 한 줄
- 서버: 0차곡 = 사업 DEM 2 m 솎음 D8 흐름누적(웅덩이 메움 · 평지 넘김 · numpy) · 위 집수 0.1~1 ha 칸 세선화 선 · 10 m 미만 토막 거름 · 파생 저장본(격자 지문 · 임계) · 임계 = 요청 min_m2 · max_m2
- 화면: 단추 셋(bolt · lineDashed · arrowsJoin 부품 상자에 더함) · 색 --draw-map-fault · lineament · zero-order · 범례 = 가닥 · km · 첫 셈 늦으면 알림 · 바탕 캔버스
- 선 긋기 · 그림 겹 놓기 · 그림 읽기를 LayerDefs 로 옮김(700줄 한도)
- test_553_4: 겹 목록에 faults · lineaments · zeroOrder 더함(새 겹 · 기대 목록 갱신)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:54:51 +09:00
eomsangdonandClaude Opus 5.5 00ea1cf298 knowledge(요구): 요구 443 원문 기록 — 브이월드 겹 넣음 · 0차곡 · 임도망도 · 산사태취약지역 더함
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:44:00 +09:00
eomsangdonandClaude Opus 5.5 be956cbdc1 chore(계획): PLAN 583 이관 — 새 구조물 측점 평면 비탈 끝 · 면적 실시간 = 저장 뒤(요구 441)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:36:32 +09:00
eomsangdonandClaude Opus 5.5 0fcdfdf754 chore(계획): PLAN 581 이관 — D03 GIS 겹 여덟 더함 · 이용 조건 확인(요구 440-8 · 442)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:17:14 +09:00
eomsangdon 0949e07a27 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-29 15:16:47 +09:00
eomsangdonandClaude Opus 5.5 f29fb01dd5 feat(D03 겹): 노선분석 지도 산림입지도 겹 — 브이월드 WFS 토양형 · 묶음 셋째 · 처음 끔 (PLAN 581-2 · 요구 440-8)
- 서버: lt_c_fsdifrsts 받은 범위로 잘라 토양형 코드 · 이름 · 넓이 · 토양형별 합 · 덮으면 다시 안 받음 · 실패 = 502
- 화면: 단추 하나(잎 아이콘 · 부품 상자에 더함) · 토양형 색 --draw-map-soil-1~6 · 범례 = 토양형 이름 · ha · 바탕 캔버스
- 산사태위험지도(생활안전지도 = 공공누리 4유형 · 데이터 준비 중) · 단층 · 지질구조선(이용 조건 문서 없음)은 넣지 않음(브레인 결정 A)
- test_553_4: 겹 목록에 soils 더함(새 겹 · 기대 목록 갱신)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:15:10 +09:00
eomsangdonandClaude Opus 5.5 f4a7b183b0 fix(평면): 새 구조물 측점 놓는 즉시 평면 비탈 끝 · 면적 = 저장 뒤 — 튐 없음 · 옮김 계획고 (PLAN 583-1 · 요구 441)
- 원인: 구조물을 새 측점에 놓으면 측점 갈아끼움(비동기)이 끝나기 전 묶음 cross 단계가 옛 상세를 봐 엔진 길로 빠짐 — 구조물 얹기 없는 한 차례(벽 측점 비탈 끝 35 · 55 m · 기존 벽 측점 18.96 m)가 약 80 ms 평면에 튐 · 다음 차례(0.25 s)에 저장 값
- design_shared savedCross = 도는 갈아끼움(swapInFlight)을 기다려 그 상세로 다시 셈 · 상세 가게 swapInFlight 새로
- 종단도 listed() 옛 측점 = 저장 정본 상세 — 갈아끼운 상세로 셈해 옮긴 구조물 옛 측점 꼭짓점이 안 옮겨져 계획고 최대 2.4 cm 갈리던 것(옮김 면적 차 0.17 → 0.0175 ㎡)
- 시험: test_272_swap_keeps_canon 에 도는 갈아끼움 기다림 · test_473_lane_ready 글 대조를 새 호출 꼴로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:05:16 +09:00
eomsangdonandClaude Opus 5.5 63a9f16d32 merge(D03 겹): dev 합침 — 581-1 겹 일곱 + 582 두 장 캔버스 + 579 · 580 그리기 나눔 양쪽 살림
- UI_Layers.ts: 582 바탕 캔버스(`base`) + 581-1 새 겹 일곱 · 겹 열둘 모두 `base: true` · 글 폭 기억 · drawRaster 둘 다
- UI_Style.css: 582 `.d03-plan__base` 블록 + 581-1 `--d03-layers-w` 글
- test_553_4: 580 DrawPaint 단언 + 581-1 createMapTools · LayerDefs 단언
- test_582: 바탕 겹 수 5 → 겹 모두(12) — 581-1 합침으로 낡은 수 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:55:18 +09:00
eomsangdonandClaude Opus 5.5 12080c1664 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 — 582 두 장 캔버스 · UI_Map 충돌(sub1 누름 잡기 · 놓음 + sub2 위만 다시 그림) 양쪽 살림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:49:32 +09:00
eomsangdonandClaude Opus 5.5 441f08e095 chore(계획): PLAN 579 · 580 이관 — 예정노선 복수 시종점 · 노선 그리기 끝(요구 440 · 441)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:47:55 +09:00
eomsangdonandClaude Opus 5.5 86507df52f feat(D03 겹): 노선분석 지도 새 겹 일곱 — 음영기복 · 물 면 · 기존 임도 짐작 · 보호 · 규제 구역 · 계류 · 도로 띠 · 건물 · 지명 (PLAN 581-1 · 요구 440-8 · 442)
- 겹 단추 = 공용 지도 도구줄(createMapTools) 한 줄 · 묶음 셋(바탕 · 물 · 길 · 구역) · 넘치면 가로 밀기 · 오른쪽 도구줄 자리 = 겹 줄 폭(--d03-layers-w)
- 새 겹 처음 값 = 끔 · 켬끔 기억(aislo:pref:d03.layers) · 받기 실패 = 그 겹만 꺼짐 + 알림
- 범례 = 켠 겹마다 한 줄(급경사 비율 · 보호구역 종류별 곳 · ha · 띠 ha · % · 물 면 · 건물 · 임도 짐작 수)
- 면 겹 채움 · 겹 정의 = D03_PlanAnalysis_UI_LayerDefs.ts(700줄 한도로 나눔)
- 부품 상자: 아이콘 shield · borderOuter · 그림 색 --draw-map-* 열넷
- 음영기복 빈 칸 투명(HILLSHADE_VERSION 2)
- 시험 test_553_4_de_parts 의 D03 겹 단추 대조 고침 — 겹 단추가 createToolbar 에서 공용 지도 도구줄로 · 겹 열셋으로 바뀌어 옛 대조가 낡음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:47:51 +09:00
eomsangdon 4c14654bbc Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-10-29 14:47:03 +09:00
eomsangdon c4f5f3c715 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-29 14:46:16 +09:00
eomsangdonandClaude Opus 5.5 1ddb5cb1b2 feat(D03): 예정노선 그리기 — 시점 · 종점 여럿 · 들어올 때 자동 받기 · [긋기] 양끝 · 부분 수정 · 기울기 % · ° 미리보기 · 방향 바꾸기 · 선 고르기 · 지우기 · 연도 (PLAN 579-3 · 580-1~3 · 요구 440 · 441)
- 579-3: D02 시점 · 종점 목록 받음 · 점 그림 · 맞춤 · 등고선 범위 · 「범위 밖」 판정에 시점 · 종점 · 들어올 때 받은 범위가 못 덮으면 바로 받기(진행 막대 · 직전 실패면 안 함)
- 선 이름 = 갈래 없는 선 「본선」(둘 이상 「본선 N」) · 갈래 「갈래 N」 — TS lineNames ↔ D04 line_names 짝 · 거울 시험
- 580-1: [긋기] 하나 — 시점 · 종점(끝낼 때 뒤집어 No.0 = 반대쪽) · 선 꼬리 · 선 머리 · 선 위(갈래)에서 시작 · 머리가 다른 선 꼬리에 닿으면 한 선 · 꺾임점이면 갈래 · 꺾임점 끌기 · 더하기 · 지우기(마크는 구간 비율)
- 580-2: 미리보기 글 = 거리 + 가상 종단 기울기 % · ° · 기울기 고정 칸 옆 ° 환산 · 고른 선 [방향 바꾸기]
- 580-3: 지도에서 선 · 조각 고름(좌측 줄 밝힘) · Delete · [선 지움] · 선 통째 연도 · 긋는 해(긋는 중 바꾸면 다음 점부터) · [연도 구분] 뒤 연도 고름 바로 · 마크 끌기
- 새 파일: D03_PlanAnalysis_UI_Edit.ts(고르기 · 고치기) · D03_PlanAnalysis_UI_DrawPaint.ts(그림 · 집기 — UI_Draw 700 줄 넘어 나눔)
- UI_Map = 겹 꼴에 release · pointerup · 끌기 중 포인터 잡기 · [?] 안내 줄(그리기 본체 draw() 는 sub2 몫이라 손 안 댐)
- 시험: 530-3 새 꼴 함수 · 서버 derive 대조 · 선 이름 거울 / 553-4 그림 자리 옮김

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:45:10 +09:00
eomsangdonandClaude Opus 5.5 5e906ceb44 perf(D03): 노선분석 평면 두 장 캔버스 · 한 화면 한 번 그리기 · 등고선 줌 단계 색인 · 글 폭 기억 — 그림 그대로 (PLAN 582-0 · 582-1 · 요구 440-9 · 441)
- D03 평면 = 바탕(위성 · 바탕 겹 다섯) / 위(그은 선 · 재기) 두 장 · 마우스 움직임 · 누름 = 위만 · 화면 바뀜 = 둘 다 · rAF 한 화면 한 번
- PlanMap.redraw = 위 겹만 · redrawBase = 바탕까지(겹 켬끔 · 받음)
- 공용 그리기에 선택 칸만 더함 — 조각 색인(part.lod · indexLayerLod 를 부른 레이어만) · 글 폭 기억(widths 인자) · B04 · B05 는 안 켬
- 한 판 한 번 긋기(d)는 뺌 — 반투명 선 겹침 색이 바뀌고(픽셀 14만 차) 래스터가 더 느림(36 → 60 ms)
- 지킴 시험 test_582_contour_lod — 색인 그린 명령 = 지금 명령(27 줌 · 자리 · 닫힌 고리 · 짧은 조각) · 글 폭 기억 같은 글

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:41:59 +09:00
eomsangdonandClaude Opus 5.5 23aacda55e feat(D02): 기준지정 시점 목록 · 종점 목록 화면 — [+ 시점] [+ 종점] · 줄마다 지움 · 점 잇는 붉은 선 걷음 (PLAN 579-2 · 요구 440)
- 표 = 시점 묶음 · 종점 묶음(짝 없이 따로 · 최소 한 줄 · 마지막 줄 지움 = 비움) · 표지 이름 「시점 1」「종점 2」
- [+] = 빈 줄 더하고 지도 누름이 거기 놓음 · 캐시 꼴 {base, points: {starts, ends}} · 옛 두 칸 캐시는 버림
- 정보 줄 = 시점 n · 종점 m · 저장 좌표계

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:32:41 +09:00
eomsangdonandClaude Opus 5.5 9c8710a934 feat(D03 겹): 보호 · 규제 구역 여덟 종 · 계류 100 m · 도로 300 m 띠 · 음영기복 · 도엽 건물 · 물 면 서버 받기 (PLAN 581-1 · 요구 440-8 · 442)
- 보호구역 = 브이월드 WFS 겹 이름 열(분석 440-C 표 1) · 받은 범위로 잘라 종류 · 이름 · 넓이 · 종류 하나 실패 = 그 종류만 빠짐
- 띠 = 하천중심선(세류 뺌) 100 m · 도로중심선(미분류 · 소로 · 면리간도로 뺌) 300 m 합친 띠 · 넓이 · 범위 비율 · 파생 저장본
- 음영기복 = B07 hillshade_png · 급경사와 같은 길(raster?kind=hillshade)
- 도엽 겹 목록에 건물 · 실폭하천 · 수계 면 둘(E003 · E005) 더함
- 지킴 시험 test_581_1_plan_gis_layers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:29:49 +09:00
eomsangdonandClaude Opus 5.5 80e3f5c738 feat(D02·D03): 기준지정 시점 · 종점 목록 서버 꼴 — plan/points {starts, ends} · 옛 한 점 꼴 감쌈 · 저장된 좌표계 그대로 (PLAN 579-1 · 요구 440)
- D02 정본 칸 plan_base_json = {epsg, starts: [{x, y}], ends: [...]} · 옛 start · end 는 읽을 때 목록으로 감쌈(옮김 없음)
- 목록마다 상한 20 · 저장된 epsg 가 있으면 그대로(점이 늘어 평균 경도가 띠를 넘어도 D03 노선과 안 어긋남)
- 부르는 곳 없던 연도 잇기 코드(chain_years · with_tm · changed · _store) 걷음 — 시험도 지움
- D03 지형 입구 = 시점 · 종점 모두 · 정본 굳힘 = 새 꼴

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:18:14 +09:00
eomsangdonandClaude Opus 5.5 b137d5adb8 knowledge(요구): 요구 442 원문 기록 — 겹 단추 한 줄 그대로 · 넘치면 가로 밀기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:16:42 +09:00
eomsangdonandClaude Opus 5.5 c4b37023c7 knowledge(요구): 요구 441 원문 기록 — 440 결정 · 새 구조물 측점 실시간 반영 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:11:14 +09:00
eomsangdonandClaude Opus 5.5 efb449a94e chore(계획): 요구 440 요구서 · 분석 셋 · PLAN 579~582 승인 대기 — 예정노선 설계 · GIS 겹 · 등고선 성능
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:41:16 +09:00
eomsangdonandClaude Opus 5.5 addce07aa5 chore(계획): PLAN 578 이관 — 화면 = 저장(격자 자리) · 평면 비탈 끝 = 횡단 최종 모양(요구 439)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:34:06 +09:00
eomsangdon 52f4b6db49 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-29 13:33:25 +09:00
eomsangdonandClaude Opus 5.5 acc09b0410 fix(B05·B06): 지형 격자 자리 = 서버 표본기와 같은 params DTM 행 · 격자 없는 지형 안 닫힌 측점 「지형 범위 밖」 배지 (PLAN 578-1 · 요구 438 · 439)
- 까닭: 확정 모델 행이 stage 1 params 와 어긋나면(검증_sub1 확정 = sheet · params = classification) terrain-sources 의 grid = null → 브라우저는 미교차 넓힘을 못 하고 ±20 m 에서 멈춤 · 서버 표본기 · Node /calc 는 params 를 따라 넓힘 → 소단 밀린 측점 화면 ≠ 저장
- 고침: `_confirmed_model` → `_design_model` — params 의 `dtm_{filter}.npz` DTM 행(확정 행 먼저) · 표본 폭 상수는 그대로(통째 격자가 이미 최대)
- 횡단 카드: 격자 없는(DTM 아닌) 지형에서 사면 미교차 = 부품 배지 「지형 범위 밖」(값은 [저장] 때 서버)
- 시험: test_344_1_terrain_grid_route 에 어긋난 확정 행 지킴 · test_133_1 · test_505_2 바뀐 이름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:32:24 +09:00
eomsangdon 7d03530225 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-29 13:31:48 +09:00
eomsangdonandClaude Opus 5.5 ee49754b46 fix(평면): 벽 측점 비탈 끝 윤곽에 벽 윗면 · 앞면 그대로 — 3D 벽 도형 땅 만남과 0 차 (PLAN 578-2)
- 성토 위 이음 → 전면 발끝 대각선 대신 벽 도형 앞 위 → 앞 밑(buildRevetWallGeometry 차례) · 이음 안쪽 0.1 m 만큼 갈리던 것

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:31:03 +09:00
eomsangdonandClaude Opus 5.5 0c415aca6b knowledge(요구): 요구 440 덧 — 신규 요구서 · 계획서 · 조사부터
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:23:30 +09:00
eomsangdonandClaude Opus 5.5 d8b93a4ea3 knowledge(요구): 요구 440 원문 기록 — 예정노선 설계 개선 아홉(복수 시종점 · 그리기 · 연도 · GIS · 등고선 성능)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:22:32 +09:00
eomsangdonandClaude Opus 5.5 bfa2b3a97f fix(평면): 평면 비탈 끝 · B07 계획평면도 = 벽 측점 횡단 최종 모양이 땅과 만나는 점 (PLAN 578-2 · 요구 438 · 439)
- B06 STRUCTURE_ROW_KEYS · 서버 _AREA_KEYS 에 slope_end_left_m · slope_end_right_m — 벽 트림 측점만 · 트림 사면 → 단마다 벽 앞 → 사이 성토부선 윤곽이 처음 땅에 닿는 오프셋
- 평면 발자국 finalSlopeEnd · B07 final_slope_end(짝) — 얹은 값 먼저 · 없으면 설계선 끝(구조물 없는 측점 그대로)
- 시험 test_578_2_plan_slope_end 새로 · test_346_2 거울에 벽 측점 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:13:39 +09:00
eomsangdonandClaude Opus 5.5 8fcc4c9fbe knowledge(요구): 요구 439 원문 기록 — 578 지형 범위 넓힘 · 평면 비탈 끝 = 횡단 최종 모양 진행
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:02:02 +09:00
eomsangdonandClaude Opus 5.5 a791f7f988 knowledge(요구): 요구 438 원문 기록 — 소단 비탈 끝 화면 ≠ 저장 고침 방향 · 평면 비탈 끝 구조물
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:00:47 +09:00
eomsangdonandClaude Opus 5.5 98023a717e chore(계획): PLAN 577 이관 — 소단 = 옹벽처럼 측점 얹기 한 벌 · 사본 걷음 · 3D 까지(577-2 · 요구 437)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:05:00 +09:00
eomsangdonandClaude Opus 5.5 403722e687 refactor(B05·B06): 소단 = 옹벽처럼 측점에 제원 얹음 — 형상 얹기 한 벌 · 세션 사본 berm 걷음 (PLAN 577-2 · 요구 434 · 437)
- 얹기: attachShapes · attach_shapes 에 소단(section.berm · 덮는 소단 없음 = null) · SHAPE_KEYS 에 berm · TS attachBermSpecs ↔ 파이썬 attach_berm_specs · berm_spans_from 짝
- 읽기: 저장 노선 다시 셈(브라우저 · Node cross.refresh) = 얹힌 section.berm(칸 없음 = 저장분 design.berm) · 노선 초안 · 서버 Node 묶음 cross = crossWithShapes 의 얹힌 제원(bermAt · crossWithBerm 걷음)
- 계단은 계산 안쪽 그대로 · [저장] 길(구조물 정본 → cross.berm_spans) 그대로
- 지움: 시설 줄 syncBerm · putBerm(C4.copy) · writeBermSpans · 세션 칸 berm · bermRequest · design_shared bermAt
- 시험: test_272_berm_source 측점 얹기 거울로 · test_323_4 소단 얹음 · 477 · 511-A · 472 떠남 · 365 · 272 고름 헬퍼가 사본 대신 얹힌 소단 · design_pipeline_page 700 줄 → 699(주석 한 줄)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 11:33:06 +09:00
eomsangdonandClaude Opus 5.5 189474a507 knowledge(요구): 요구 437 원문 기록 — 577 가 = 소단도 옹벽처럼 측점에 얹음 · 3D 까지
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 11:14:26 +09:00
eomsangdonandClaude Opus 5.5 31e1debcf6 chore(계획): 요구 436 분석 2차 — 소단 대 옹벽 · 흙막이 길 비교 · 권함 가(측점에 얹음)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 10:59:33 +09:00
eomsangdonandClaude Opus 5.5 0081353dd0 knowledge(요구): 요구 436 원문 기록 — 577 사면안정 그룹끼리 비교 · 검증_랩탑메인 초기화
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 10:54:11 +09:00
eomsangdonandClaude Opus 5.5 95dd1f0253 chore(계획): PLAN 575 이관 — 노선 손 고침 [저장] 종단 = 옛 종단 길이 비례(575-2 · 요구 435)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 09:40:54 +09:00
eomsangdon 08a1bef47b Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-29 09:40:11 +09:00
eomsangdonandClaude Opus 5.5 22ccd1085f chore(계획): 요구 434 분석 — 사면안정 구조물군 길 · 소단만 사본 = 옛 흔적 · PLAN 577
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 09:31:25 +09:00
eomsangdonandClaude Opus 5.5 f5dbb6e3dc fix(종단): 노선 손 고침 [저장] 바뀐 구간 계획고 = 옛 종단 길이 비례(지반 아님) · 손 [저장]이 「종단 반영」 안 덮음 (PLAN 575-2 · 요구 432 · 434 · 435)
- carry_base(파이썬) ↔ carryBase(TS) 짝 — 같은 자리 = 맞춘 옛 누가거리 · 바뀐 자리 = 앞뒤 같은 자리(없으면 시점 0 · 종점 새 연장 ↔ 옛 연장) 사이 새 누가거리 비례로 옛 누가거리 · 높이 = 옛 기준 꼭짓점 · 지반 첫 계획선은 측점 자리만
- 덧 흠 — 손 [저장](as_initial 아님)은 initial_choice with_profile 을 적힌 값 그대로
- 낡은 시험 고침: test_400 · test_344_2 「바뀐 자리 = 지반」 · 「끝 꼭짓점 = 새 지반」 → 옛 종단 비례 · 옛 끝 높이 / test_459 비균형 「옮긴 시설 측점 = 새 원지반」 단언 지움(옛 규칙 · 시설 세움은 옮긴 계획선 높이) · 거울 단언은 그대로
- 새 시험: test_400 바뀐 자리 비례 4 갈래 · test_346_3 손 [저장] 종단 반영 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 09:25:12 +09:00
eomsangdonandClaude Opus 5.5 61373d70b0 knowledge(요구): 요구 435 원문 기록 — 575 B 진행 · 사면안정 구조물군 로직 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 09:15:10 +09:00
eomsangdonandClaude Opus 5.5 9deeadf599 knowledge(요구): 요구 434 원문 기록 — 575 B 의견 · ↶ 개념 · 소단만 사본인 까닭
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:56:38 +09:00
eomsangdonandClaude Opus 5.5 2af3816f98 chore(계획): PLAN 477 이관 — ↶ 바로 켜짐 · 소단 사본 결과 통(477-3 · 477-4 · 요구 433)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:16:12 +09:00
eomsangdon 61b5016f19 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-29 07:15:24 +09:00
eomsangdonandClaude Opus 5.5 ffa762438f fix(B05·B06): 소단 사본 berm = 결과 통 — 초안 칸 · ↶ 사진 · 떠남 제외 줄에서 빠짐 (PLAN 477-4 · 요구 433)
- berm 은 구조물(소단)에서 셈한 값 · 손 고침 길 없음(쓰는 곳 = 페이지 시설 줄 하나) → 등록부 결과 통으로
- 떠남 확인 제외 목록의 berm 줄 지움(초안 통이 아니라 필요 없음)
- 쓰는 곳 없는 readBermSpans 지움 · 읽는 곳(시설 줄 · 다시 셈) 이름 그대로
- 지킴 시험 test_477_undo_now_berm(즉시 켜짐 · flush · 묶음 · berm 통)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:14:40 +09:00
eomsangdonandClaude Opus 5.5 78188cf7a3 fix(B05·B06): 조작 놓은 직후 ↶ 바로 켜짐 · 기다림 중 ↶ = 먼저 찍고 되돌림 (PLAN 477-3 · 요구 433)
- 찍기 기다림(400 ms) 들어가면 한 틱 뒤 단추 맞춤 — 세션이 지금 걸음과 다를 때만 켜짐(바뀐 것 없는 누름은 그대로 꺼짐)
- ↶ ↷ 누르면 기다리던 찍기 먼저(누름 중이어도) — 방금 조작이 한 걸음으로 되돌아감
- 기다림 시간 · 서버 다시 셈 묶음은 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:14:34 +09:00
eomsangdonandClaude Opus 5.5 d37db94224 knowledge(요구): 요구 433 원문 기록 — 477-2 ③ ↶ 늦게 켜짐 · ④ 소단 사본 지금 고침
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 06:58:15 +09:00
eomsangdonandClaude Opus 5.5 10fba8983c chore(계획): PLAN 576 이관 — MC 측점 일반 측점 위 = 스냅대로(576-1 · 요구 432)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:11:48 +09:00
eomsangdonandClaude Opus 5.5 f0bd6906da chore(계획): PLAN 469 이관 — 관 · 구조물 전부 지우고 떠남 = 물음(469-3 · 요구 432)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:00:47 +09:00
eomsangdon b2712ddb7a Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-28 23:00:38 +09:00
eomsangdon 5ec7887137 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-28 23:00:10 +09:00
eomsangdonandClaude Opus 5.5 7e0580ffd1 fix(노선 편집): MC 를 일반측점 위에 두면 그 뒤 스냅대로 — 「위」 판정 폭 = 측점 목록 「(MC)」 규칙 · 밀어내는 걸음 막음 (PLAN 576-1 · 요구 432)
- StationFit: 일반측점(측점 간격 배수) 「위」 = curveStations 반올림 폭(mcScale 공유 · 정수 m ±0.5 m) · 폭 안이면 정확히 맞춰 보고 못 맞춰도 「위」 · 10 m N+10 · 간격 격자는 옛 0.05 m 그대로
- offStationCount · offStationMcs 에 측점 간격 인자 · 편집기 · 곡선 띠가 넘김(1 m · 10 m 스냅 동작 전후 같음)
- pushedOff — R · 곡선 길이 입력 등 applyEdit 걸음이 측점 위 곡선을 밖으로 밀면 되돌림(꺾임점 · 곡선 켬 같은 걸음만)
- 시험: test_432_mc_on_station 새로 · helper_366_3 가 편집기처럼 측점 간격을 넘김(옛 helper 는 간격=측점 간격 가정) · test_390_5 배선 문구 offStation 한 곳으로 바뀜
- build:design-calc 다시 지음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:59:17 +09:00
eomsangdonandClaude Sonnet 5.5 481f79f5ee fix(B05·B06): 관 · 구조물을 전부 지우고 떠나도 묻는 창이 뜨게 함 (PLAN 469-3 · 요구 432)
- 빈 목록 초안 "[]" 판정을 draftFilled 한 함수로 — 떠남 확인(design_leave)과 hasDrafts 가 같은 규칙
- 저장분이 있는 목록 칸의 "[]" = 고침 · 저장분도 빈 곳 · 저장분을 모를 때 = 전처럼 빈 칸
- 저장분 있음은 페이지 자료 줄(design_page_data)이 받을 때 적음 — 새 서버 요청 없음
- 지킴 시험 test_469_3 · 자료 줄 흉내 헬퍼 둘에 새 import 흉내

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 22:51:17 +09:00
eomsangdonandClaude Opus 5.5 7b8cc69606 chore(계획): 요구 432 분석 B — MC 측점 되돌아감 원인 · PLAN 576(판정 폭 ±0.5 m · 막음)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:48:14 +09:00
eomsangdonandClaude Opus 5.5 dbe975b603 chore(계획): 요구 432 분석 A — 노선 손 고침 [저장] 뒤 종단 지반고 = 옛 규칙(498-2 아님) · PLAN 575 · 469-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:45:12 +09:00
eomsangdonandClaude Opus 5.5 dd197a9754 knowledge(요구): 요구 432 원문 기록 — 검증_랩탑메인 종단 지반고 재조사 · MC 측점 일반 측점 위 두면 스냅 따라감
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:33:14 +09:00
eomsangdonandClaude Opus 5.5 8cbd5dc468 chore(계획): PLAN 498 이관 — 노선 고친 [저장] 계획고 = 서버 옮김 위 사용자 손댐만(498-2 · 요구 417)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:18:10 +09:00
eomsangdonandClaude Opus 5.5 03b31ff53a chore(계획): PLAN 456 이관 — 종단 도구 고름 화면 흠 셋 고침 끝(456-3 · 요구 421 · 423~425)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:03:49 +09:00
eomsangdon 94decdfc32 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-28 22:03:38 +09:00
eomsangdon d44c5873dd Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-28 22:03:07 +09:00
eomsangdonandClaude Sonnet 5.5 1241ad6d17 fix(종단): 도구 고름 화면 흠 셋 — 고른 즉시 붉은 강조 · 스크롤 따라감 · 측점선 누름 전파 막음 (PLAN 456-3 · 요구 421 · 423~425)
- design_profile: 도구 고름 뒤 종단도 한 번 다시 그림
- RunHighlight · Render: 붉은 선을 계획선 세로 창 겹 안에 그림
- Render: 도구가 먹은 누름은 전파 막음 — 측점 고름 안 돎
- Panel_Tools: 도구 바꿀 때 겹 안 붉은 선도 같이 뗌
- test_271: 지킴 시험 더함

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 22:02:25 +09:00
eomsangdonandClaude Opus 5.5 7a3107919f chore(계획): PLAN 464 이관 — surface_params 대비 불필요(서버가 늘 채움) · 닫음(요구 431)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:00:34 +09:00
eomsangdonandClaude Opus 5.5 5da29d2afc knowledge(요구): 요구 429 · 430 원문 기록 — ORCA 탭 메인 빌려 씀 = 지금 유지 · 464 설명
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:59:15 +09:00
eomsangdonandClaude Opus 5.5 1b901a8872 chore(계획): PLAN 460 이관 — 검증_랩탑메인 추천 [적용] · 최대 기울기 12.36 %(요구 426~428)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:51:56 +09:00
eomsangdonandClaude Opus 5.5 b92fa06f34 fix(B05·B06): 노선 고친 [저장] 계획고 = 서버 옮김 위에 사용자 손댐만 (PLAN 498-2 · 요구 411~417)
- 브라우저 — 노선 바뀐 [저장]의 계획고 본문 = 묶음 계획고 대 저장분 옮김(같은 carryEdits · 이 초안 첫 기준)의 다른 열쇠 · 지운 열쇠만(`gradeTouch` · `routeGradeTouch`) · 못 가르면 옛 길(노선 0 칸 통째)
- 서버 — `alignment_cleared` 가 오면 관 · 구조물 · 추가 측점 차례 뒤 새 종단 편집(서버 옮김) 위에 손댐만 얹어 한 번 저장(`touched_alignment` · 얹기 실패도 되돌림 안)
- follow 옮김 측점 = stations 단계와 같은 추가 측점(`plus` 넘김)
- 재현: 꼬리 추가 측점 ▲ 뒤 앞 교점 끌기 [저장] = 990 m 편집 버림 · ▲ 잃음 · 최대 −0.50 m → 고친 뒤 버림 0 · 끌 때 = 저장 뒤
- 시험: test_498_2_grade_touch 새로 · helper_design_store_save 에 묶음 창구 가짜 하나(옛 길)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:51:29 +09:00
eomsangdonandClaude Opus 5.5 4e2dce461d knowledge(요구): 요구 423~425 원문 기록 — 종단 쉬프트 고름 강조 · 스크롤 · 측점선 누름 고침 동의
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:46:51 +09:00
eomsangdonandClaude Opus 5.5 be1cbf636f chore(계획): 요구 421 분석 — 종단 쉬프트 고름 강조 늦음 · 스크롤 안 따라감 원인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:43:10 +09:00
eomsangdonandClaude Opus 5.5 f4c8fde77b knowledge(요구): 요구 422 원문 기록 — B07 횡단 장 선 삐짐 = 사용자 판단 · 후보 아님
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:40:57 +09:00
eomsangdonandClaude Opus 5.5 cee7d4ccf9 chore(계획): PLAN 574 이관 — B07 서버 묶음 B06 화면 코드 걷음 끝(요구 418)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:38:26 +09:00
eomsangdon 1ddb773910 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-28 21:37:21 +09:00
eomsangdonandClaude Opus 5.5 4910fbaccc refactor(B07): 횡단 덧그림 서버 묶음이 B06 화면 허브를 끄지 않게 함 (PLAN 574-1 · 요구 418)
- Overlay 가 설계선 · 포장 그리기를 허브(_UI_Cross_Design) 대신 _Design_Line · _Design_Surface 에서 직접 가져옴
- 허브가 상세 창 · CAD · API · 부품 상자를 끌던 사슬 걷힘 — 묶음 482 → 240 KB
- 구조물 그림 여섯은 이미 logic 기하만 씀 · SVG 그리기는 B07 이 SVG → CAD 로 옮기므로 그대로 둠 · 가짜 DOM 계속 필요
- 지킴 시험 test_574 — 묶음 입구에서 닿는 TS 에 화면 코드 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:36:42 +09:00
eomsangdonandClaude Opus 5.5 415c3fe4bb knowledge(요구): 요구 421 원문 기록 — 종단 쉬프트 고름 붉은 강조 늦음 · 스크롤 안 따라감
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:35:15 +09:00
eomsangdonandClaude Opus 5.5 e2ed10fc1a chore(계획): PLAN 433 이관 — 새 초기값 경고 칩 사용자 확인 끝(요구 420)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:32:55 +09:00
eomsangdonandClaude Opus 5.5 b1afec7162 chore(계획): PLAN 412 이관 — 깎기 깊이 · 쌓기 높이 한도 안 둠(요구 419)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:29:04 +09:00
eomsangdonandClaude Opus 5.5 76bb0c3fd3 chore(계획): 472 밖 후보 4(↶ ↷ 접힘) 지움 · 6 = PLAN 574(요구 418)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:26:15 +09:00
eomsangdonandClaude Opus 5.5 9c18177eb3 knowledge(요구): 요구 418 원문 기록 — 472 밖 후보 4 유지 · 6 고침(PLAN 574)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:25:49 +09:00
eomsangdonandClaude Opus 5.5 d9b9b7d3be chore(계획): PLAN 472 이관 — B05 · B06 구조 다시 세움 끝(472-8 · 요구 409)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:12:40 +09:00
eomsangdon 615fcfaac4 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-28 21:11:58 +09:00
eomsangdon 5d172622bd Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-10-28 21:11:04 +09:00
eomsangdon 69f6c7e0b7 Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_1 2026-10-28 21:10:22 +09:00
eomsangdonandClaude Opus 5.5 2da632dc45 refactor(B05·B06): 부름 0 옛 줄 · 늘 건너뛰는 시험 지움 (PLAN 472-8 ② ③ · 요구 409)
지움
- flushDesignStore(design_store · 부름 0) — 시험 344-4 의 flush 대조 한 줄도
- b_page_state 옛 세션 열쇠 옮김(legacy · migrate) · b_project_switch 옛 키 머리
  — 옛 열쇠는 세션 저장소(탭 닫으면 사라짐) · 지금 코드가 안 씀 · storage · DB 에 없음
  · 시험 290-1 · 401 · 384-1 의 옛 키 줄 뺌
- 시험만 부르던 TS — planFootprint(346-2 거울은 footprintRecord + slopePieces 로 그대로)
  · shiftSegment · shiftMovingPoints(쉬프트 단추는 09-02 지움 · min_cover_guard 쉬프트 시험 뺌)
  · dropStationsNear(test_b05_station_merge.mjs 지움) · createIrregularStationsSection(부름 0)
  · generalStations(234-1 은 기본 맞춤 값으로 같은 것 잼) · sameSummaryItem
- test_b05_b06_save_roundtrip + helper_b05_save_payload 둘 — 프로젝트 936be972 storage 없어 늘 skip
  · 같은 저장 왕복은 test_472_6_design_save(창구 원자성) · test_449_save_pipes(관 PUT)
    · test_238_1_save_keeps_structure_stations(구조물 측점)가 잼

남김
- migrateLegacyStations — 부르는 곳 있음(design_view_profile_sections) · storage 에 비정규 측점 남음
- common_util_excavation 셋 — 지금은 운영이 부름(design_calc_node)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:09:50 +09:00
eomsangdonandClaude Opus 5.5 4609ff0d7f fix(B06): 단면 유형 없는 옛 설계 기본값 = 상단측 깎기 하나 (PLAN 472-8 ① · 요구 409)
- 브라우저 다시 셈(Cross_Refresh_Calc) · 묶음 cross 단계(Cross_Stage) 의 좌 깎기 고정 폴백 걷음
  · cross 단계는 이미 있는 uphillSide(Pipeline_Detail) 다시 씀
- 서버 [저장] 두 길도 같은 규칙 — 파이썬 stored_section_mode(Router_Design 셋 · SampleExtend)
  · Node Cross_Save storedOptions · Node 에 측점 uphill_side 넘김(Server_Calc_Prebuild)
  · 화면만 상단측이면 [저장] 뒤 좌 깎기로 뒤집혀 같이 고침
- 값 바뀜 = section_mode 없는 설계뿐(지금 DB 16 측점 · 한 프로젝트) · 그 밖 0 차
- 거울 시험 test_472_8_section_mode_default(파이썬 = Node = 브라우저 · 없음 = 상단측 적음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:09:50 +09:00
eomsangdonandClaude Opus 5.5 cbef2d9b16 knowledge(요구): 요구 411~417 원문 기록 — 498-2 계획고 덮어쓰기 = 저장 때 입력만 보냄으로 고침
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:09:33 +09:00
eomsangdonandClaude Opus 5.5 ca9ec14c39 chore(계획): PLAN 350 이관 — 끌기 · 다른 시간 지금 만족 · 일단 완료(요구 410)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:52:43 +09:00
eomsangdonandClaude Opus 5.5 027b2f5cf4 chore(계획): 요구 407 분석 — 498-2 계획고 덮어쓰기 남음 · 492 이관 글에 493 걷음 표시
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:47:32 +09:00
eomsangdonandClaude Opus 5.5 5567a148fa knowledge(요구): 요구 409 원문 기록 — 472-7 브레인 권함대로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:44:58 +09:00
eomsangdonandClaude Opus 5.5 a49c3da918 chore(계획): PLAN 492 이관 — 492-3 엄상준_검증 찌꺼기 정리 끝(사용자 [적용] · 요구 408)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:40:14 +09:00
eomsangdonandClaude Opus 5.5 919a2eb1ee knowledge(요구): 요구 406 · 407 원문 기록 — PLAN 498-2 계획고 덮어쓰기 재확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:35:50 +09:00
eomsangdonandClaude Opus 5.5 c1cf362517 chore(계획): PLAN 572~573 이관 — 프로젝트마다 따로 동작 · 검증 프로젝트 고정 · 옛 검증 14 지움
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:47:35 +09:00
eomsangdonandClaude Opus 5.5 aed9857714 feat(공용): 프로젝트 떠남 = 물음 뒤 그 프로젝트 비움 (PLAN 572-4 · 요구 400~403)
- main.ts renderAll 첫머리 — 프로젝트 화면 → 밖이면 leaveProject(지금 프로젝트) · hashchange 가드(셋 물음)를 지난 뒤
- leaveProject 지움 = b_page_state clearSessionKeys(지움 알림) — B05 설계 가게의 미룬 쓰기(1 s)가 떠난 뒤 design-footprint 를 되살리던 흠 막음(ORCA 에서 찾음)
- test_401 — 떠남 연결 소스 대조 · 지움 알림 수

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:44:36 +09:00
eomsangdonandClaude Opus 5.5 f427aa76f6 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 — 572-3 D · E 가드 · 시험 충돌(양쪽 살림 · PENDING 빈 set)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:23:18 +09:00
eomsangdon 62b63e2053 Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_3 2026-10-28 18:20:35 +09:00
eomsangdonandClaude Opus 5.5 2b1f54aa4c fix(D03): 못 읽은 채 떠남 = 고침 없음 — 처음 깨끗한 값 = 빈 선 (PLAN 572-3)
- 프로젝트 없음 · 불러오기 실패 때 savedJson "" 라 dirty 참 → 떠남 셋 물음이 잘못 뜸(ORCA 실측)
- test_560_4 한 줄 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:20:12 +09:00
eomsangdon 3748b7edfd Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-28 18:18:21 +09:00
eomsangdonandClaude Opus 5.5 6d7657aeb9 fix(공용): 떠남 [취소] 뒤 단계 [<] [>] 흐려짐 — 주소 되돌림 알림 (PLAN 572)
- main.ts 취소 갈래 = replaceState 뒤 aislo:route-restored 냄(hashchange 가 안 뜸)
- ui_template_help [<] [>] sync 가 그 알림에도 다시 맞춤 · 화면에서 빠지면 둘 다 풂
- test_401 소스 대조 하나 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:17:56 +09:00
eomsangdonandClaude Opus 5.5 d9d96c62de chore(시험): test_401 PENDING 에서 572-2 로 고친 B 파일 열둘 뺌 (PLAN 572-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:11:06 +09:00
eomsangdonandClaude Opus 5.5 c3fbcb13f1 feat(D · E): 떠남 = 공용 물음 [취소 · 폐기 · 저장] · 캐시 = 등록표 · 프로젝트 = 탭 currentProjectId (PLAN 572-3 · 요구 400~403)
- D02 · D03 · E02 말없이 저장 → mountGroupStep {dirty, save, discard} 공용 물음
- D04 · E05 · E06 가드 새로 · E03 = 대표 단면 별 저장 진행 중일 때만 물음
- D02 · E02~E07 세션 직접 읽기 · 쓰기 → 등록표 d02-points · e02-ground · e03-prob · e04-settings · e05-report · e06-rock · e07-settings
- 아홉 페이지 localStorage 지금 프로젝트 직접 읽기 → currentProjectId() (다른 탭이 바꿔도 이 탭 그대로)
- test_560_4 D · E 줄 넓힘(옛 beforeLeave 단언 → dirty · save) · test_401 PENDING 에서 D · E 뺌

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:09:08 +09:00
eomsangdon e065c765ea Merge remote-tracking branch 'origin/dev' into sub_laptop_2 2026-10-28 18:08:05 +09:00
eomsangdonandClaude Opus 5.5 676b46eb9e feat(B 페이지): 떠남 물음 가드 · 지금 프로젝트 = 탭마다 (PLAN 572-2 · 요구 400~403)
- B04 · B07 · B08 · B09 가 공용 guardProjectLeave 에 {dirty, save, discard} 등록 — 고침 있을 때만 [취소 · 폐기 · 저장]
  - B04 = 관 지점 고침(drainageDirty) · 저장 = 관 지점 · 세부유역 영구저장
  - B07 = CAD · 탭 고침 · 저장 = saveEditedDrawings · 폐기 = 고친 도면 CAD 자동백업도 버림(discardEditedRecovery)
  - B08 = 세션 b08-* 칸 넷 · 저장 = saveShown · B09 = b09-estimation 칸 · 저장 = saveChanged
- 지금 프로젝트 직접 읽기(localStorage) → currentProjectId() · B02 새 프로젝트 = setCurrentProject — B02 · B03 · B04 · B07 · B08 · B09 · B11
- 다른 프로젝트 보관함 지우기 셋(B04 · B05 종단 · B11) 뺌 — enterProject 한 곳 · 다른 탭 보관분 안 지움
- B02 · B03 은 가드 없음 — 저장 안 한 프로젝트 값 없음(B03 올리기 끊김은 이어 올리기 배너가 받음)
- 시험 test_560_4 넓힘(B 가드 · 탭 프로젝트 · 보관함) · helper_298_2 · helper_481_2 · test_204_2 흉내를 currentProjectId 로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:07:58 +09:00
eomsangdon a98533cf39 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-28 18:06:40 +09:00
eomsangdonandClaude Opus 5.5 2ba46341ee feat(공용): 프로젝트마다 따로 동작 공용 — 탭 프로젝트 · 떠남 비움 · 떠남 물음 한 벌 (PLAN 572-1 · 요구 400~403)
- b_project_switch: currentProjectId(탭 세션 먼저 · 새 탭 = 마지막 값) · setCurrentProject · forgetProject · leaveProject(그 프로젝트 세션 칸 · 준비 표식 · 3D 보관함 몫) · reloadIfOtherProject(다른 프로젝트 화면 = 새로 엶) · guardProjectLeave · askProjectLeave([취소 · 폐기 · 저장] 한 번) · linkRouter(라우터가 이어 줌) · 화면 모듈은 부를 때만 들임
- b_asset_cache purgeProject · b_page_state 등록표 D02 · E 일곱 칸(옛 키 한 번 옮김) · router isProjectRoute
- 가드 helper: design_leave = 공용 가드 위 껍질(B05 고침 지문에 D · E 칸 안 섞임) · group_workflow { dirty, save, discard } · b_workflow_nav · b_page_scaffold · overlay · design_store · design_page 직접 읽기 → currentProjectId
- 시험 test_401_project_isolation 새로(탭 둘 흉내 · 떠남 · 보관함 · 가드 · 직접 읽기 한 곳) · 290_1 시드에 D02 · E 옛 키
- 낡은 시험 고침: 298_3 · 346_4 · 384_s · 520_3 헬퍼 = 공용 가드 · 탭 프로젝트 흉내 더함 · 298_3 탭 닫기 듣개 자리 b_project_switch 로 · 302_1 라우터 판별 글 isProjectRoute 꼴로 · 343_3 헬퍼 가짜 IndexedDB 내보냄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:06:14 +09:00
eomsangdon 4c7b57c97e Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_4' into main_laptop_1 2026-10-28 17:29:07 +09:00
eomsangdonandClaude Opus 5.5 6f95797250 feat(복제 도구): D(예정노선) · E(안정성평가) 프로젝트 통째 복제 · 종류 자동 판별 · --dry-run (PLAN 573-1 · 요구 404 · 405)
- D 표 넷(plan_networks · plan_route_pieces · plan_route_years · plan_route_assignees) 복제 · 업체 지정 id 새로 · 이은 임도 끊음
- E 원본 칸 · 단계 행 · 파일은 projects 행째 · 폴더째
- 종류(project_kind → B · D · E) 판별 · 목록 밖 project_id 표 경고 · manifest 에 종류 · 빠진 표
- --dry-run = 종류 · 표마다 행 수 · 폴더 크기(쓰지 않음)
- 새 시험 test_573_1_clone_de — 임시 storage · 끝에 hard_delete_project

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:25:12 +09:00
eomsangdonandClaude Opus 5.5 92d952d8ac fix(B04 · B06): 유입 캐시 상한 넷 · 흙막이 보낸 값 열쇠에 프로젝트 · 노선 (PLAN 572-2)
- B04 유입 배열 캐시 = 최근 프로젝트 넷만(_DETAIL_CACHE 꼴) · 서버 켠 뒤 본 프로젝트마다 쌓이던 것
- B06 soilSent 열쇠 = {프로젝트}:{노선}:측점:역할 · 다른 프로젝트 · 노선 같은 측점에 같은 값 안 먹던 흠
- 시험 test_572_2_inflow_cache 새로 · test_54_10 다른 프로젝트 같은 값 · helper_387_4b scope 칸

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:24:07 +09:00
eomsangdonandClaude Opus 5.5 45b8188121 chore(요구): 요구 405 — 창마다 그룹당 검증 프로젝트 하나 · 복사로 만듦 · 다른 탭 허용 · PLAN 572 · 573
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:18:56 +09:00
eomsangdonandClaude Opus 5.5 ecbc6a69bf chore(요구): 요구 403 · 404 조사 — 동시 프로젝트 · 검증 프로젝트 목록
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:12:04 +09:00
eomsangdonandClaude Opus 5.5 64d9f8b2c0 chore(요구): 요구 404 — 검증 프로젝트 무분별 생성 문제
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:00:34 +09:00
eomsangdonandClaude Opus 5.5 b6365d9327 chore(요구): 요구 403 — 프로젝트 ID 원칙 · 떠날 때 3D 도 비움 · 묻기 동의 · 동시에 여는 프로젝트 수 제한 검토
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:58:10 +09:00
eomsangdonandClaude Opus 5.5 b2b481ae6c chore(요구): 요구 402 — 프로젝트 ID 열쇠 원칙 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:53:13 +09:00
eomsangdonandClaude Opus 5.5 1a8d83b05b chore(요구): 요구 401 다시 조사 — 프로젝트마다 따로 동작
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:47:44 +09:00
eomsangdonandClaude Opus 5.5 c9ecf5bb69 chore(요구): 요구 401 — 프로젝트마다 따로 동작 · 3D 도 다른 프로젝트로 묻어가지 않게 · 다시 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:35:26 +09:00
eomsangdonandClaude Opus 5.5 975673758b chore(요구): 요구 400 조사 — 프로젝트 떠날 때 세션 비우기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:53:32 +09:00
eomsangdonandClaude Opus 5.5 3908e110e9 chore(요구): 요구 400 — 전역 싱크 · 설명서는 사용자 지시 때 · 프로젝트 떠날 때 세션 비우기 조사
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:44:42 +09:00
eomsangdonandClaude Opus 5.5 e96219b801 chore(계획): PLAN 570~571 이관 — 임시 배포 빌드판 도구 · 지도 도구줄 가로 밀기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:09:57 +09:00
eomsangdon 8b29e1ff76 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-28 12:09:29 +09:00
eomsangdonandClaude Opus 5.5 61b799eefd feat(tools): 빌드판 포트 지정 · 개발판 자리에 빌드판 (PLAN 570-1 · 요구 398)
- preview_up.py --port N · --replace-dev(빌드가 된 뒤에만 개발 포트 vite dev 를 끄고 그 자리에 빌드판)
- 빌드 실패 = 아무것도 안 끔 · 옛 빌드판이 그 자리면 끄고 개발판으로
- 기본(개발 포트 − 1000) 그대로 · 지킴 시험 test_570_1_preview_up
- (git 밖 .claude) dev_up.py --build · git-sync-all.ps1 -Global 데스크탑 메인 = WMI 로 dev_up --build · 5173 빌드판 확인

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:08:47 +09:00
eomsangdonandClaude Sonnet 5.5 f4c43db01b feat(지도 도구줄): 좁으면 한 줄 가로 밀기 · 넘친 쪽 끝 흐림 · 휠 가로 밀기 · D03 도구줄이 겹 단추줄을 안 덮음 (PLAN 571-1 · 요구 399)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 12:07:52 +09:00
eomsangdonandClaude Opus 5.5 70b0e06df4 chore(요구): 요구 399 — 지도 도구줄 좁으면 줄바꿈 대신 가로 밀기
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:01:59 +09:00
eomsangdonandClaude Opus 5.5 fb6ee44f18 chore(요구): 요구 398 — 임시 배포(데스크탑) 5173 = 빌드판 · 랩탑은 개발판 그대로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:59:15 +09:00
eomsangdonandClaude Opus 5.5 e1f4b9e9ad chore(요구): 요구 397 — 가이드 프로젝트는 사용자가 지움 · 모바일 빌드 포트 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:27:41 +09:00
eomsangdonandClaude Opus 5.5 b2f5662de7 chore(가이드 · 계획): 가이드 road 프로젝트 id 바로잡음(옛 가이드_용화 없음) · PLAN 568~569 이관 — 요구 395 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:36:30 +09:00
eomsangdon 2a85f73463 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-10-28 07:35:56 +09:00
eomsangdonandClaude Sonnet 5.5 7933a643b2 chore(가이드): B05 · D03 도구 아이콘 글 · 그림 다시 + D03 지도 겹 글 한 편 (PLAN 568-4 · 요구 395)
- B05 평면 도구 줄 아이콘 14 (번호 표 · 마우스 글) · D03 도구 아홉(06_도구_여섯 → 06_도구_아홉)
- D03 「지도 겹 켜고 끄기」 새 글 · 겹 아이콘 그림 · 급경사 15° · 35° · 40° 뜻
- 그림 다시: B05 8 장 · D03 12 장 + 새 2 장(D03_17 · 18) · 캡처 실패 0

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 07:35:21 +09:00
eomsangdonandClaude Opus 5.5 08f06c6afc chore(요구): 요구 396 — 이번 일 뒤 설명서는 사용자 지시 때 한 번에
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:16:45 +09:00
eomsangdon 663fe9345f Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_3' into main_laptop_1 2026-10-28 07:10:58 +09:00
eomsangdonandClaude Opus 5.5 200149553a fix(D03 겹): 급경사 범례 = 서버 단계 색 · 넓이 비율 그대로 · 지적 선 분홍 · 지형 없을 때 빈 범례 숨김 (PLAN 569-2)
- 범례가 응답 bands(색 · 비율)를 그대로 씀 — 화면 급경사 색 값 셋 뺌(test_551_2 그림 값 개수 24 → 21)
- 지적 경계 노랑 → 분홍(등고선 · 급경사 노랑과 겹쳐 안 보임)
- 겹 판 만들 때 상태 한 번 맞춤 — 지형 못 읽으면 범례 빈 칸이 남던 것

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:10:42 +09:00
eomsangdonandClaude Opus 5.5 dee16cab37 feat(B05): 평면 도구줄 = 공용 지도 도구줄 아이콘 14 (PLAN 568-2 · 요구 395-1)
- 글자 유리 단추 줄 · [?] 안내 판 · 나눔선 다섯 → createMapTools 한 줄(긴 설명은 마우스 글 · 누름 동작 · 단축키 그대로)
- 3D 단추 = mapToolButton("view3d") · 클래스 b05-route__3d-open 유지 · 거리 재기 aria-pressed 맞춤
- 가이드 촬영 선택자(.b05-routeedit__tools · [data-act="hint-toggle"] · .b05-routeedit__hint.is-open) 그대로
- 시험 고침 — 옛 글자 단추 · 나눔선 · 안내 판 CSS 를 잡던 다섯(차례 · 안내 판 · 절/성토선 이름 · 3D 막이)
- 부품 — 빈 묶음 건너뜀 · [hidden] 숨김 규칙

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:10:21 +09:00
eomsangdon fd61446f72 Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_3' into main_laptop_1 2026-10-28 07:04:51 +09:00
eomsangdonandClaude Opus 5.5 42b63663fa fix(D03 겹): 지적도 열쇠 상자 반올림 뺌 — 같은 범위인데 겹 켤 때마다 다시 받던 것 (PLAN 569-1)
- 열쇠 상자를 소수 셋째 자리로 줄여 받은 범위를 못 덮는다고 봄 → 실제 값 그대로 저장
- 시험: 소수 끝자리까지 같은 상자 두 번 = 한 번만 받음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:04:03 +09:00
eomsangdonandClaude Opus 5.5 b4e1b89943 feat(D03): 평면 도구줄을 공용 지도 도구줄로 — [?] 안내 · 측점 · 기울기 켬끔 더해 아홉 · 겹 단추 아이콘 (PLAN 568-3 · 569-2 · 요구 395)
- UI_Map — createMapTools · mapToolButton(되돌리기 · 다시하기 · 3D · 돌리기 둘 · 측점 · 기울기 · 거리 재기) · [?] 조작 안내 여덟 줄 · ScreenMap 에 측점 · 기울기 켬 실음
- UI_Draw — 측점 눈금 · 구간 길이 · 기울기 글을 켬끔 따라 그림
- UI_Layers — 겹 단추 글자 대신 공용 아이콘(satellite · droplet · borderAll · angle)
- test_553_4 — 옛 createToolbar · createIconButton 지킴을 공용 도구줄 · 겹 단추 지킴으로 바꿈

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:04:02 +09:00
eomsangdon 7f17af513c Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-10-28 07:01:46 +09:00
eomsangdonandClaude Opus 5.5 6cec62c75b feat(D03): 노선분석 지도 바탕 겹 켜고 끔 — 위성 · 등고선(계곡선 굵게) · 세류 · 지적 · 도로 · 급경사 (PLAN 569-2 · 요구 395-2)
- 새 D03_PlanAnalysis_UI_Layers.ts — 왼쪽 위 겹 단추 한 줄 · 처음 켬 = 위성 · 등고선 · 세류 · 고른 값 aislo:pref:d03.layers 기억 · 켤 때만 받음 · 급경사 범례
- UI_Map — 등고선 그리기를 겹으로 옮김 · ScreenMap.view 더함
- Api — 569-1 서버 겹 세 길(지적 · 도엽 겹 · 급경사 그림) 부름
- theme.css 그림 색 일곱(지도 겹 넷 · 급경사 셋) · test_551_2 그림 값 개수 17 → 24(새 값 일곱)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:01:36 +09:00
eomsangdon bf1e5d2985 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-28 07:00:56 +09:00
eomsangdonandClaude Opus 5.5 062d0b34a4 feat(D03 겹): 노선분석 지도 겹 서버 길 — 도엽 겹 · 지적도 1 km 조각 · 급경사 단계 그림 (PLAN 569-1)
- 새 D03_PlanAnalysis_Layers: 도엽 겹 풀어 냄 · 브이월드 WFS LX맵 1 km 조각(1,000 꽉 차면 넷 · pnu 거름) · 급경사 15° · 35° · 40° PNG(E07 slope_rad · 평활 안 한 격자 · 파생 저장본)
- Router: GET plan/layers/sheet?key= · plan/layers/cadastral · plan/layers/raster?kind=slope
- Terrain: 지형 받기 때 지적도 같이(실패해도 계속)
- B04 도엽 읽기 목록에 도로중심선 한 줄(PARSER_VERSION 그대로)
- 시험 test_569_1_plan_layers 새로 · test_530_1 가짜에 지적도 막음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:00:10 +09:00
eomsangdonandClaude Opus 5.5 38c40f70bc feat(ui_template): 지도 도구줄 부품 · 아이콘 여덟 · 부품 모음 견본 (PLAN 568-1 · 요구 395-1)
- ui_template_map_tools.ts · .css — 아이콘 단추 한 벌(차례 · 나눔선 · 켬끔 aria-pressed · [?] 안내 판)
- 아이콘 더함 — ripple · tallymarks · percentage · magnet · satellite · droplet · borderAll · angle(Tabler MIT)
- 부품 모음 틀 묶음에 「지도 도구줄」 견본 · 시험 test_568_1 · test_552_4 목록에 map-tools

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 06:59:33 +09:00
eomsangdonandClaude Opus 5.5 7bc33ded6e chore(요구): 요구 395 분석 둘 — 화면 제어 단추 한 벌 · 노선분석 지도 겹
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 06:53:57 +09:00
eomsangdonandClaude Opus 5.5 2eb1c761a1 chore(요구): 요구 395 — B05 평면 화면 제어 단추 = D03 와 같은 아이콘 · D03 겹(도엽 등고선 · 위성 · 세류선 · 지적도) 켜고 끔
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 06:39:05 +09:00
eomsangdonandClaude Opus 5.5 fa96baa5c0 chore(요구): 요구 394 — 깃 싱크 · 서버 다시 켬(전역 아님)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 06:24:46 +09:00
eomsangdonandClaude Opus 5.5 82bef5380c chore(가이드 · 계획): 가이드 프로젝트 id 바로잡음(stab · plan 옛 id 없음) · PLAN 560~567 이관 — 요구 389~393 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 23:59:30 +09:00
eomsangdon c458b9fc9d Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-10-27 23:56:59 +09:00
eomsangdon f553abfb75 auto: 2026-10-27 23:54 (ESD_LAPTOP) 2026-10-27 23:54:38 +09:00
eomsangdonandClaude Sonnet 5.5 6d9be022ab feat(가이드): E01 · E02 사용자 가이드 다시 쓰기 · 그림 다시 찍기 (PLAN 567-3 · 요구 390)
- E02 = [해석 시작] · 진행 막대 · 단계 줄 · [끊기] · [저장] 없음 · 떠날 때 저장 · 단계 차례 · 측점 No.+거리
- E02_16 진행 막대 장면 더함(진행 응답 흉내) · 단추 줄 선택자 고침

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:54:37 +09:00
eomsangdon 90d02699e4 Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_7' into main_laptop_1 2026-10-27 23:49:23 +09:00
eomsangdonandClaude Sonnet 5.5 c743b90b43 feat(E01): 빠진 측점 띠 간격 = 측점 목록 추정값(E02 · E03 부르는 줄)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:48:11 +09:00
eomsangdonandClaude Sonnet 5.5 c9d052ae4b feat(가이드): E04 · E05 가이드 다시 쓰기 · 그림 다시 찍기 — 건기 · 우기 따로 · 활동체 기둥 수 · 보고서 그림 셋 · 판정 면 칸 · 열 때 저절로 갱신 (PLAN 567-3 · 요구 390)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:47:21 +09:00
eomsangdon 21c6a5b282 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-27 23:45:51 +09:00
eomsangdonandClaude Sonnet 5.5 785cae47b0 feat(가이드): E03 2D 해석 가이드 다시 쓰기 · 그림 다시 찍기 — 탐색 한계 · 판정 면 · 위험 띠 확대 · 대표 단면 바로 저장 (PLAN 567-3 · 요구 390)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 23:02:14 +09:00
eomsangdonandClaude Sonnet 5.5 a82c6d6704 feat(가이드): E06·E07 사용자 가이드 다시 쓰기 · 그림 다시 찍기 (PLAN 567-3 · 요구 390)
- 단계 차례(지반 → 2D → 3D → 암반 → 광역 → 보고서) · [<] [>] · [해석] = 저장 · [저장] [다시 해석] 뺌
- 측점 No.+거리 · 광역 흐름 D∞ · E02 물성 · 보고서 자동 갱신 글
- 장면 단추 줄 호출 번호 고침 · 그림 다시 찍음

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 22:52:24 +09:00
eomsangdonandClaude Sonnet 5.5 cc4330d946 feat(567-1): E05 보고서 판정 면 — 측점표 판정 방법 · 면 확인 · 모형 끝 면 칸 · 대표단면 조건별 판정 면 · 탐색 한계 한 줄
- 측점표(s4) 오른쪽에 판정 방법 · 면 확인 · 모형 끝 면 칸 셋 + 풀이 한 줄(탐색 한계 포함)
- 대표단면(s5) 조건별 방법 · 임계면 그림 · 부록 절편 = E03 판정 면(원호 Bishop · 비원호 Spencer · M-P 중 작은 값)
- 입력조건 해석 방법 글 · 탐색 한계 한 줄 갱신
- 시험 534_3 판정 면 경우 덧

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 22:13:49 +09:00
eomsangdon 0fca2b5af1 Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_7' into main_laptop_1 2026-10-27 21:46:53 +09:00
eomsangdonandClaude Sonnet 5.5 86939a6eec feat(가이드): D01~D04 사용자 가이드 다시 쓰기 · 그림 다시 찍기 (PLAN 563-3 · 요구 389)
- D01 생성 칸 셋(시작 연도 · 사업 지역 · 이름 자동) · 그림 여섯
- D02 전체 시점 · 종점 두 점 + [일반][위성] · 저장 뒤 이동 · 그림 아홉
- D03 손 긋기(점 찍기 · 미리보기 · 제약 넷 · 종점 붙기 · 갈래 · 구역 마크 + 조각 연도 · 도구 여섯 · 지형 범위 미리보기 · 3D) · 옛 후보 · 횡단 글 지움 · 그림 열일곱
- D04 조각 단위(조각 표 · 조각마다 업체 · 내보내기 · 연결) · 그림 여덟

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 21:46:11 +09:00
eomsangdonandClaude Opus 5.5 04e041ee5c fix(564-1): 체인 광역 단계 = E07 화면과 같은 조건(지난 조건 + 지금 E02 물성) · 지문에 E02 물성(564-4)
- 시험 test_564_1 — 광역 물성 E02 값 · 지문 같으면 건너뜀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 21:32:46 +09:00
eomsangdon 937dfd8786 Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into main_laptop_1 2026-10-27 21:24:47 +09:00
eomsangdonandClaude Sonnet 5.5 b371a707f2 feat(567-4): E04 저장본 기둥 수 · 100 미만 경고 옮김 + 결과 칸 「활동체 기둥 수」 줄 · 배지
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 21:24:23 +09:00
eomsangdonandClaude Opus 5.5 75155c50b0 feat(564-1 · 564-3): 체인 단계 상태 = mark_stab_step(565-1) · E02 떠날 때 바뀐 값 저장(beforeLeave 560-4)
- 체인 단계마다 run → done · skip · error · cancel 을 대시보드 단계 행에 적음 · 지반 = 시작 때 done · 적기 실패는 체인을 막지 않음
- E02 beforeLeave — 고칠 수 있고 바뀐 값이 있으면 PUT /stab/ground 뒤 이동 · 틀린 칸 · 실패 = 머묾
- 시험 test_564_1 — 단계 상태 적기 차례 · 적기 실패 격리

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 21:24:21 +09:00
eomsangdon 4860e11794 feat(E03): 탐색 한계 고르기를 왼쪽 판 분할 고르기로 · 「이 결과 탐색 한계」 한 줄 · 바꾼 뒤 해석 전 표시 (PLAN 567-4) 2026-10-27 21:21:01 +09:00
eomsangdonandClaude Opus 5.5 f1e9113562 chore(요구): 요구 393 — 2D 탐색 범위 고름 = 왼쪽 판
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 21:14:01 +09:00
eomsangdonandClaude Sonnet 5.5 0fc0f0aae2 feat(561-2): D 예정노선 지형 격자 2 m · B 공용 1 m 그대로 (grid_m 인자)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 21:05:42 +09:00
eomsangdon 2ba29afb79 Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_3' into sub_laptop_1 2026-10-27 21:02:34 +09:00
eomsangdonandClaude Opus 5.5 f71906cd21 fix(563-2): B03 가져오기 창 제목 「지정받은 예정노선 조각 고르기」
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:58:57 +09:00
eomsangdonandClaude Opus 5.5 04e254f1ec fix(565-2): 광역 위험 지도 범례 등급 이름 = E07 화면 범례(위험 하한 · 위험 상한)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:58:51 +09:00
eomsangdon 8d7493c5be Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-27 20:54:35 +09:00
eomsangdon ea3acc5be2 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-10-27 20:54:34 +09:00
eomsangdonandClaude Sonnet 5.5 b1a1658f97 feat(562-1): 기울기 고정 = 마우스에 가장 가까운 자리 · 짧은 첫 자리 대신
- 반직선 위 |기울기| 목표 자리 전부 모아 마우스 거리 가까운 자리 선택(없으면 없음)
- 셈 짝 TS ↔ Py 같이 · 거울 시험에 여러 자리 경우 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 20:51:52 +09:00
eomsangdonandClaude Sonnet 5.5 a931b6988f feat(E03): 목록 판정 면 칸을 별 뒤로 · 짧은 배지 (PLAN 567-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 20:50:52 +09:00
eomsangdon 388d283b48 Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-10-27 20:47:48 +09:00
eomsangdonandClaude Opus 5.5 2ffff11087 fix(567-1): E03 단면 화면 임계면 방법 이름 — 후보 고리 변수가 판정 방법을 덮던 것
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:42:54 +09:00
eomsangdon 7aac010fb6 Merge remote-tracking branches 'origin/sub_laptop_2', 'origin/sub_laptop_3', 'origin/sub_laptop_4' and 'origin/sub_laptop_7' into main_laptop_1 2026-10-27 20:42:07 +09:00
eomsangdonandClaude Opus 5.5 cddafbf369 feat(565-2): E05 보고서 그림 셋(3D 안전율 지도 · 스테레오넷 · 광역 위험 지도) · 열 때 해석 바뀌었으면 저절로 갱신 · 566-1 평면 위험 띠 측점 글 No.+거리
- 새 E05_StabReport_Maps.py — E04 구간 격자 · E07 등급 격자는 PNG(셀 = 화소)로 SVG 안에 · E06 판정 나쁜 측점 평사투영 · 3D요약 · 광역요약 · 암반검토 표 밑에 「n. 제목」 줄 + 그림 한 행
- GET 보고서: 고칠 수 있는 회사면 낡은 작업본을 store.refresh 로 갱신(refreshed · overwritten) · 보기만 회사는 띠 그대로 · 화면 알림
- 시험 534_3: 3D · 광역 견본에 격자 · 그림 닻 · xlsx 그림 수 · PDF 3D 2쪽 · 저절로 갱신 · 스테레오넷 측점 고름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:41:56 +09:00
eomsangdonandClaude Opus 5.5 604808ede7 fix(562-3): D03 갈래 시작 = 가장 가까운 꺾임점 · 제약 기울기 칸 = 오르내림 같은 크기
- 갈래 · 마크 누름: 붙는 반경 안 꺾임점 중 첫 것이 아니라 가장 가까운 것(짧은 구간이 붙어 있으면 앞 점이 잡히던 흠)
- 562-1 짝이 |기울기|로 셈 → 칸 이름 「기울기(± 같음)」 · 자리 없음 안내 ±값

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:41:00 +09:00
eomsangdonandClaude Opus 5.5 f558f5d2ba feat(563-2): D04 표 = 조각 줄(연도 · 선) · 조각마다 업체 · B03 가져오기 · B01 받은 예정노선 = 조각 이름
- D04 업체 추가 · 저장에 조각 id · 옛 연도 지정 업체 = 그 연도 첫 줄
- B03 [예정노선에서 가져오기] 줄 · B01 받은 예정노선 연도 칸 · [연결] 제목에 「본선 0+0~12+10」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:39:12 +09:00
eomsangdonandClaude Sonnet 5.5 79d33601f2 feat(E03): 판정 면 칸(원호 · 비원호 · 면 확인 · 모형 끝 면) + 탐색 한계 고르기 (PLAN 567-1)
- 목록 판정 면 칸 · 결과 방법 줄 · 면 확인 · 모형 끝 면 배지
- 해석 설정 탐색 한계(모형 끝 / 비탈 끝 + 2H / 비탈 끝 + 정한 m) — [해석] 이 저장 + 그 값으로 셈
- Router 설정 저장본 · 지문 · 일꾼 kwargs · 단면 · 가정 · 확률 길

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 20:38:55 +09:00
eomsangdonandClaude Opus 5.5 7c94294f85 feat(563-2): D04 확정 · 업체 지정 · 내보내기 · 연결 = 조각 단위 — 옛 연도 행 프로젝트 그대로
- 단위 = plan_route_pieces 한 줄 · 조각 없는 연도 행 = 연도 하나 · 확정은 연도 단위 그대로
- 요약 pieces(이름 「본선 0+0~12+10」) · 업체 행 piece_id · 지정 회사 = 자기 조각만
- 내보내기 폴더 = 연도_선_조각 · 전체 = 선마다 레코드 · 좌표표 선 · 조각 칸
- 확정 메일 = 업체마다 자기 조각 zip · 연결 · 가져오기 = 조각 꺾은선
- 받은 예정노선 목록 = 조각 이름 · 길이
- 시험 531 둘 = 조각 없음 가짜 더함 · 새 563_2 지킴 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:37:18 +09:00
eomsangdon 97bdc0a92d Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-27 20:30:09 +09:00
eomsangdon 69f8f24ded Merge remote-tracking branches 'origin/sub_laptop_7' and 'origin/sub_laptop_8' into main_laptop_1 2026-10-27 20:28:59 +09:00
eomsangdon 80948e0199 Merge remote-tracking branches 'origin/sub_laptop_7' and 'origin/sub_laptop_8' into sub_laptop_2 2026-10-27 20:28:55 +09:00
eomsangdonandClaude Opus 5.5 fb48bd74fb fix(E03): 위험 띠 고른 측점 곁 솎은 글 겹침 뺌 (PLAN 566-2)
- planLabelKeys — 고른 측점 반 걸음 안 글은 뺌(원배율에서 24+0 · 25+0 겹침)
- 시험 532_3 logic TS 에 고른 측점 곁 뺌

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:26:50 +09:00
eomsangdonandClaude Opus 5.5 9d67c3eba6 feat(567-2): E04 임계 활동체 기둥 수 · 100 미만 경고 · 기본 설정 Scoops3D 재현 시험 · E07 집수면적 D∞
- E04 best · stats 에 columns · few_columns(< 100 · Scoops3D limcol)
- 시험 533_3: 기본 설정 그대로 · 상자 없이 제방 30° 1.3203 · D&G 1a 1.0385 · 제방 60° 0.7488(±2 %) · 기둥 경고
- E07 집수면적 = D∞(Tarboton 1997 · SINMAP 원본) · 내리막 없는 채운 평지만 D8
- 시험 536_2: D∞ 면 몫(22.5° = 반씩) · 30° 돌린 평면 줄무늬 표준편차 0.05(D8 0.16)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:25:43 +09:00
eomsangdonandClaude Opus 5.5 a843aa3ad3 feat(567-1): E03 2D 비원호 출발 셋 최솟값 · 판정 = 원호 Bishop · 비원호 Spencer · M-P 중 작은 값 · 면 확인 · 모형 끝 면 · 탐색 한계 모형 끝
- 비원호 꼭짓점 최적화를 난수 씨앗 셋으로 돌려 찾은 면 모두에서 방법별 최소(NONCIRC_STARTS 3)
- 판정 면 = 원호 Bishop · 비원호 Spencer · M-P 중 작은 값 · 비원호 < 원호 Bishop × 0.85 = surface_check · 판정 면 진입 · 진출이 모형 끝 1 m 안 = edge
- 탐색 한계 바깥 끝 = 모형 끝(요구 392 · Slide2 기본) · 설정 search_reach(model_end · 2h · m)
- 새 지킴 시험 test_567_1(원호 기본 대 촘촘 ≤ 0.5 % · 비원호 ≤ 3 % · 다출발 최소 · 한계 · 판정 칸)
- 낡은 시험 고침: ACADS 3a Spencer · ACADS 4 · M-P ACADS 4 비원호 탐색 허용 ±5 % — 다출발이 원문보다 낮은 면(약층 바닥 따라감 · 3a Spencer 1.217)을 찾음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:25:21 +09:00
eomsangdon e1fb8ad09c Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-27 20:24:02 +09:00
eomsangdon 01583467d7 Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_2 2026-10-27 20:23:26 +09:00
eomsangdonandClaude Opus 5.5 0909db020f feat(E): 단계 차례 보고서 맨 뒤 · DB 단계 행 여섯 · 체인 단계 상태 함수 (PLAN 565-1)
- 차례 = 지반 · 2D · 3D · 암반 · 광역 · 보고서 — STAB_STEP_ROUTES · 단계 막대 이름 · 아이콘 · 대시보드 요약 이름 같은 차례
- 단계 행 STABILITY 여섯(STAB_AREA 더함) · 022_stab_stages_six.sql = 옛 행 열쇠로 번호만 바꿈 · 없는 광역 행 NOT_STARTED · 다시 돌려도 같음
- mark_stab_step(project_id, step, state, message) — 체인 낱말(ground · 2d · 3d · rock · area · report × run · done · skip · error · cancel) → 단계 행 상태 · 열쇠로 찾음 · 실패 까닭만 남김
- E05 빈 상태 글 = E02 [해석 시작] · 보고서 안내에 암반 · 확률
- 시험 528 단계 여섯 · 상태 함수 · 280_2 가짜 차례 여섯 + 진짜 차례 끝 = E05

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:23:16 +09:00
eomsangdonandClaude Opus 5.5 3767049ae8 feat(566-1): E05 보고서 측점 = No.+거리(61+14.5) — 공용 파이썬 짝 common_util_station.py(TS stationLabel · inferStationInterval 거울) · 이름 사전 누가로 셈 · 해석 때 굳은 이름보다 사전 먼저(광역 · 보강 · 암반 · 확률)
시험 534_3 기대 「No.n」 → 「n+0」(견본 label 칸은 이제 안 읽힘) · 545_1 공용 짝 거울 덧.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:23:15 +09:00
eomsangdon ad7638042d Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_4' into main_laptop_1 2026-10-27 20:21:42 +09:00
eomsangdonandClaude Opus 5.5 ebc7922a8a fix(560-4): D02 서버 경위도 소수 7 자리로 받음 — 되돌아온 값 37.699999… 꼴 없음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:20:43 +09:00
eomsangdonandClaude Opus 5.5 1bb7a2c957 feat(563-1): D03 떠날 때 선만 저장(beforeLeave) · 시점 · 종점은 network 응답 base 로
- 단계 아이콘 · [<] [>] · 머리 메뉴 · 뒤로 = 고친 선이 있으면 PUT plan/network 뒤 이동 · 실패면 머묾
- D02 points 따로 부르지 않음(562-2 GET network 의 base · year_start)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:18:43 +09:00
eomsangdon 65e7a404b8 Merge remote-tracking branch 'origin/dev' into sub_laptop_4
# Conflicts:
#	resources/tester/test_530_3_plan_logic.py
2026-10-27 20:17:00 +09:00
eomsangdon 628c293eff Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_4 2026-10-27 20:16:28 +09:00
eomsangdon 9788d9f472 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_4 2026-10-27 20:16:27 +09:00
eomsangdon 0e7bc80b4e Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_4 2026-10-27 20:16:26 +09:00
eomsangdon b99d478e2e Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_4 2026-10-27 20:16:24 +09:00
eomsangdon 6e4a987ead Merge remote-tracking branch 'origin/dev' into sub_laptop_1 2026-10-27 20:16:19 +09:00
eomsangdonandClaude Opus 5.5 cdcdd5f597 feat(562-3 · 563-1): D03 손 긋기 화면 — 도구 여섯 · 그리기 · 거리재기 · 구역 연도 · 횡단 판 · 예상 코리도 걷음
- UI_Map: 바탕(위성 · 등고선) + 겹(그리기 · 거리재기) · 도구줄 부품(되돌리기 · 다시하기 · 3D · 좌 · 우 회전 · 거리재기) · B05 createMapRotation · 왼쪽 끌기 이동 끔(가운데 단추 · 휠 방향 = 설정)
- 새 UI_Draw: 시점에서 점 찍어 직선 · 미리보기 길이 · 기울기 · 제약 칸(자유 · 길이 고정 · 기울기 고정 · 둘 다 · 20 m · 9 %) · 종점 붙기 · 갈래 · 구역 마크 · Enter · Esc · Backspace
- 새 UI_Measure: 두 점 길이 · 기울기 · 높이 차(지형 heightAt)
- logic/Plan: 선 · 갈래 · 조각 꼴 다루기로 다시 씀(옛 갈래 넷 · 이은 누가 걷음)
- UI_Page · Api: 지형 범위 카카오 지도 미리보기 → [이 범위 받기] · [범위 밖 더 받기] · GET · PUT plan/network · [초기화] · 조각 연도 고름 · 캐시 판 2
- UI_3D: 지형 + 그은 선(선 고름) · 예상 코리도 · 체크 칸 걷음
- 지움: UI_Cross · UI_Edit · logic/Expect · logic/Edit · test_544_1(예상 횡단 · 예상 코리도 셈이 화면에서 걷혀 지킬 대상 없음)
- 시험: 530_3 = 손 긋기 꼴 다루기 + 서버 derive 대조로 다시 씀 · 553_4 D03 부품 단언 새 화면으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:16:08 +09:00
eomsangdonandClaude Opus 5.5 f76c31b063 Merge origin/dev into sub_laptop_1 — 560-2 전체 시점 · 종점 · 562-2 노선 정본 받기 (PLAN 561-2)
- 충돌 셋 풂: D03 라우터(562-2 가 분석 API 걷음 + 561-2 지형 한 벌 · 범위 API) · 시험 530_2_plan_router 다시 씀 · 530_2_plan_analyze = dev 판
- 지형 범위의 전체 시점 · 종점 = D02 정본 load_base(plan_base_json · 빈 칸이면 옛 연도 행 접음) — 임시 _plan_inputs 감쌈 걷음
- Terrain — 옛 분석이 쓰던 연도 인자 · 복도 상수 걷음(분석 엔진 걷힘)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:16:04 +09:00
eomsangdonandClaude Opus 5.5 0c78438cb1 feat(562-2): D03 노선 정본 GET · PUT 응답에 year_start(사업연도 시작) — 조각 연도 고름 처음 값
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:15:06 +09:00
eomsangdon 2d7f7a3bd2 Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_1 2026-10-27 20:13:00 +09:00
eomsangdon 8c89f8ae82 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_1 2026-10-27 20:12:59 +09:00
eomsangdon 3e747683c0 Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_1 2026-10-27 20:12:57 +09:00
eomsangdon 88ebefde14 Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_1 2026-10-27 20:12:56 +09:00
eomsangdon 084c517060 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_1 2026-10-27 20:12:54 +09:00
eomsangdonandClaude Opus 5.5 f17db59ab6 feat(D03): 지형 = 사업 전체 한 벌 · 받기 전 범위 API (PLAN 561-2)
- 지형 받기 — 연도 구간마다 models_<연도> 대신 한 벌 models_plan · 범위 = 전체 시점 · 종점(+ 그은 점) 상자 + 여유 ∪ 받은 범위(줄지 않음) · 받은 범위 plan_terrain.json
- 도엽 = 범위 상자에 닿는 도엽 모두(3×3 이웃 대신) · 위성 = 그 도엽 도곽
- 새 GET …/plan/terrain/extent — 상자 네 귀 경위도 · 도엽 도곽 · 칸 수 어림 · 받은 범위 · covered(더 받기 판정) · 내려받기 없음
- POST …/plan/terrain 몸 = {margin_m, extra} · 여유 처음 값 350 m(옛 DEM 폭) · 바닥 300 m · 실패 글에 병합 도엽 매수
- 전체 시점 · 종점 = 임시로 첫 연도 시점 · 끝 연도 종점(_plan_ends · 560-2 받은 뒤 바꿈)
- 시험 530_1 다시 씀 · 530_2_plan_router 범위 · 받기 둘 더함 · 530_2_plan_analyze 옛 연도 DEM 기대 한 줄 고침(한 벌로 바뀜)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:12:21 +09:00
eomsangdon dd86e12d83 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-27 20:11:46 +09:00
eomsangdonandClaude Opus 5.5 bbe2079cee feat(562-2): D03 노선 정본에 전체 시점 · 종점(base) — D02 load_base 그대로 응답 · 좌표계 다르면 거절 · 옛 접음은 [저장] 전에 칸에 굳힘
- GET · PUT /plan/network 응답에 base({epsg, start, end})
- PUT network.epsg ≠ base.epsg = 400
- plan_base_json 이 비어 옛 연도 행을 접어 보이던 프로젝트 = 연도 행을 바꾸기 전에 접은 두 점을 칸에 씀(D02 시점 · 종점이 안 바뀜)
- 시험 test_530_2_plan_analyze 18 · 실제 호출 한 바퀴(검증_sub3_D)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:10:58 +09:00
eomsangdon 0193c2a0e7 Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_5' into main_laptop_1 2026-10-27 20:10:20 +09:00
eomsangdonandClaude Opus 5.5 cf7db35a54 test(564-1): 체인 시험 3D 열쇠 = 564-2 실제 section_key(가짜 뺌)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:09:14 +09:00
eomsangdonandClaude Opus 5.5 c8f7dc405a feat(560-4): 단계 이동 = 저장 뒤 이동 — mountGroupStep beforeLeave(단계 아이콘 · [<] [>] · 머리 메뉴 · 뒤로 한 길) · D02 바뀐 것 저장 · 첫 불러오기 실패에도 지도 · 옛 캐시가 서버 값 안 덮음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:08:37 +09:00
eomsangdon 68c280ff42 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-10-27 20:08:25 +09:00
eomsangdonandClaude Opus 5.5 53c30db57b feat(E01·E04·E06·E07): 측점 글 = No.+거리 소수 한 자리 · 간격 = 측점 목록 추정 (PLAN 566-4)
- 공용 stationLabel · inferStationInterval 로 한 벌(예 61+14.5) · 서버 label(STA. 꼴) 안 읽음 · 화면만
- E07 — 측점 표 · 지도 측점 글 · 셀 툴팁 · 지도 글 솎음이 20 m 고정 대신 추정 간격
- E04 — 구간 목록 · 위치(가까운 측점) · 지도 측점 글 · 툴팁 · 솎음 간격
- E06 — 위험 띠 · 측점 표 · 상세 제목 · 스테레오넷 제목 · 띠 양 끝 글
- E01 빠진 측점 띠 — interval 인자(기본 20 m · E02 · E03 이 측점 간격을 넘기면 그 값)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:07:47 +09:00
eomsangdon 84b72ad704 Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_3 2026-10-27 20:06:11 +09:00
eomsangdon 0f9579433d Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-10-27 20:06:02 +09:00
eomsangdonandClaude Opus 5.5 ba255f2851 feat(562-2): D03 노선 정본 GET · PUT network — 손 그은 선 · 갈래 · 마크 · 조각 → 조각 행 · 연도 행 · 사업연도 끝 · 옛 추천 엔진 · 꺾임점 R 걷음
- 새 D03_PlanAnalysis_Network.py — 서버가 marks 로 조각 경계 · 길이 · 측점 다시 셈(높이 저장 안 함) · 옛 연도 노선은 읽을 때 한 선으로 · 업체 지정 조각 지움 409 · 산림청만
- 새 db_management/021_plan_network.sql — plan_networks · plan_route_pieces · plan_route_assignees.piece_id(DB 반영함)
- main.py 라우터 한 벌 · Router.py 의 POST analyze · GET/PUT analysis 덩이 지움(지형 · 진행 길 그대로)
- 지움: D03_PlanAnalysis_Analyze.py · D03_PlanAnalysis_Edit.py · config PLAN_ANALYZE_WORKERS
- 시험: test_530_2_plan_analyze = 노선 정본 시험으로 다시 씀(옛 엔진 시험은 엔진과 함께 걷음) · test_530_2_plan_router = 분석 API 시험 지우고 끈 지점 규칙만 · test_530_3_plan_logic = Analyze 짝 시험 지움 · test_543_plan_edit 지움(꺾임점 R 고침 걷음 — 요구 389 11 직선만)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:05:44 +09:00
eomsangdonandClaude Opus 5.5 4cf1c61fc1 feat(560-3): D02 지도 [일반][위성] — 카카오 HYBRID · 부품 상자 분할 고르기 · 화면 취향 기억
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:05:06 +09:00
eomsangdon e9c0b85a68 Merge remote-tracking branches 'origin/sub_laptop_2', 'origin/sub_laptop_6' and 'origin/sub_laptop_7' into main_laptop_1 2026-10-27 20:04:01 +09:00
eomsangdonandClaude Opus 5.5 429275b96e feat(560-2): D02 전체 시점 · 종점 두 점 — projects.plan_base_json 정본 · 옛 연도 행 접어 보임 · 연도 행 화면 걷음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:03:35 +09:00
eomsangdonandClaude Opus 5.5 95e352205d feat(E06·E07): [해석] = 저장 · E06 [저장] · E07 [다시 해석] 뺌 · E07 엔진 판 해시 · 광역 물성 = E02 (PLAN 564-4)
- E06 — [저장] 단추 뺌 · [해석]이 고친 절리를 먼저 저장 · 안내 글 「[해석] = 저장 + 해석」
- E07 — [다시 해석] 뺌 · 엔진 판 = 엔진 · 길 파일 해시(engine_hash) · 지문에 E02 물성(ground_material)
  → E02 붕적토가 바뀌면 옛 결과 · 캐시 없이 열면 지난 조건 + 지금 E02 물성 값
- 시험 536_2 지문(E02 물성 · 엔진 해시) 함수 하나 덧

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:03:03 +09:00
eomsangdonandClaude Opus 5.5 0ea6b032a4 feat(E03): 3D 보기 설계 지형 + 노면 구멍 (PLAN 566-3)
- corridor3d 응답에 terrain 칸 — E 복사본 DTM 을 노선면 둘레 80 m 띠로 · 2 m 칸(25만 칸 넘으면 키움) · 리본 발자국 안 = None(깎기 지형이 노면을 덮지 않게) · 리본과 같은 원점 · 캐시 열쇠에 DTM 수정시각
- 3D 보기 = E04 terrainMesh 로 지형 메쉬 · 한 색(--e03-terrain 밝음 · 어두움) · [설계 지형] 켬끔 · 노면 쪽으로 겹침 밀어 둠
- 시험 532_3 노선면 설계 지형(꼴 · 구멍 · 표고 = DTM − 원점)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:02:45 +09:00
eomsangdon 391c199c69 Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_6' into sub_laptop_5 2026-10-27 20:01:10 +09:00
eomsangdon 2959023ffd Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-10-27 20:01:08 +09:00
eomsangdonandClaude Opus 5.5 bef1e8c15b feat(564-3): E02 [해석 시작] · 진행 막대 · 단계 줄 · [끊기] — [저장] 뺌 · 갔다 와도 진행 이어 봄
- [저장] 자리에 [해석 시작](POST /stab/run = 검사 · 저장 → 체인) · 잠금판은 저장 순간만
- 진행 막대(ui_template_progress) · 단계 줄 다섯(ui_template_list_row · 상태 띠 · 걸린 초) · [끊기]
- 열 때 GET /stab/run/progress — 도는 중이면 이어서 봄 · 끝난 결과면 단계 줄만
- locale_e 글 · 저장 안 됨 글 = 「해석 전」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:00:59 +09:00
eomsangdonandClaude Opus 5.5 9ec63ac4f5 feat(560-1): D01 생성 칸 여섯 걷음 · 이름 자동 「<시작 연도> <사업 지역> 노선 계획」 · 임도 종류 필수 풂 · D04 새 임도 이름 연도 한 번
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:00:41 +09:00
eomsangdonandClaude Opus 5.5 205bc275cd feat(E04): 화면 열쇠에 조건 · 건기 · 우기 결과 함께 표시 · 열 때 저장 설정 되살림 · [다시 해석] 뺌 (PLAN 564-2)
- 구간 목록 = 구간마다 건기 · 우기 안전율 둘 다 · 고른 조건 결과를 엶
- 캐시가 없으면 저장된 결과의 탐색 설정으로 칸을 채움
- [다시 해석] 뺌 — 엔진이 바뀌면 지문(엔진 파일 해시)이 바뀌어 [해석]이 다시 셈
- [기본값]이 조건을 건기로 되돌리지 않음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:00:22 +09:00
eomsangdon 0b4c34a81e Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_3' and 'origin/sub_laptop_7' into main_laptop_1 2026-10-27 19:59:22 +09:00
eomsangdonandClaude Opus 5.5 70d3a69847 fix(B04): .cpg 없는 도엽 dbf = cp949 로 읽음 · 병합 캐시 열쇠에 파서 판 번호 (PLAN 561-1)
- 도엽 파서 — zip 안 .cpg 가 없으면 encoding cp949 (latin-1 로 읽어 「등고수치」 칸 이름이 깨지던 흠)
- 병합 열쇠 {"sheets", "parser"} — B04 · D03 둘 다 · 옛 깨진 병합본은 다음 받기 때 다시 지음
- 새 지킴 시험 test_561_1 — .cpg 없는 cp949 견본 → 등고수치 읽힘
- 재현(4250c7bc tmp 사본) — 2026 구간 지형면 0 셀 → 201,088 셀 · 등고수치 빈 선 5,280 → 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:58:56 +09:00
eomsangdonandClaude Opus 5.5 ae24e29d12 feat(562-1): D03 수동 그리기 셈 짝 — 길이 · 원지반 기울기 · 길이 고정 · 기울기 고정 · 양각기 · 선 위 거리 ↔ 점 · 측점
- logic/D03_PlanAnalysis_Draw.ts ↔ D03_PlanAnalysis_Draw.py 같은 이름 짝 · 높이는 인자 heightAt
- 거울 시험 test_562_1_plan_draw.py — 두 쪽 값 대조 + 손셈 값

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:58:56 +09:00
eomsangdonandClaude Opus 5.5 7d5f516b84 feat(E03): 측점 No.+거리 · 위험 띠 확대 · 이동 · 측점 글 · 900 높이 겹침 · 대표 단면 바로 저장 (PLAN 566-2)
- 좌측 목록 · 횡단 카드 제목 = 공용 stationLabel(누가, 측점 목록 추정 간격, 소수 한 자리)
- 평면 위험 띠 = 공용 attachSvgZoomPan(휠 · 가운데 끌기 · 두 번 누름 원배율) · 측점 글 솎음(logic labelStride · planLabelKeys) · 선 굵기 · 글 크기 고정 · 배율은 다시 그려도 유지
- .e03-page 행 셋(auto auto minmax) — 화면 높이 900 에서 띠 · 본문 겹침 고침
- 대표 단면 별 = 누르면 바로 PUT representative · 뒤에서 E05 보고서 갱신 길(GET report · POST refresh) · [대표 단면 저장] 단추 · 캐시 뺌
- 시험 532_3 logic TS 에 솎음 걸음 · 글 열쇠

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:58:44 +09:00
eomsangdon e97a77082a Merge remote-tracking branches 'origin/sub_laptop_4', 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into main_laptop_1 2026-10-27 19:58:27 +09:00
eomsangdonandClaude Opus 5.5 f747e41e28 feat(E04): 3D 결과 열쇠에 조건 · 옛 이름 읽기 · 엔진 판 = 엔진 파일 해시 (PLAN 564-2)
- section_key(start, end, condition) → 0.00_100.00_dry 꼴 · 건기 → 우기가 서로 덮지 않음
- 옛 시작_끝.json 은 그 안 조건 열쇠로 읽음(load · saved_summaries)
- ENGINE_VERSION 대신 engine_hash()(엔진 파일 넷 해시) — 판 올리기 잊음 없음
- 시험 533_3 router(건기 → 우기 둘 다 남음 · 옛 이름 · 엔진 해시) · 534_3 견본 파일 새 이름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:57:56 +09:00
eomsangdonandClaude Opus 5.5 8f7bb8e7e6 feat(564-1): E02 한 번에 해석 체인 — 지반 저장 → 2D → 3D 건기 · 우기 → 암반 → 광역 → 보고서 · 진행 · 끊기
- 새 E02_StabGround_Chain.py — 페이지 해석 함수 그대로 부름 · 지문 같은 단계 건너뜀 · 단계 실패 격리 · 페이지 _JOBS 꽂음 · 3D 진행률 = 구간 × 조건 수로 나눔
- E02 Router 길 셋(POST /stab/run · GET /stab/run/progress · POST /stab/run/cancel) · PUT 과 같은 저장 함수
- 3D 열쇠 = section_key(start, end, condition) 꼴(564-2 뒤 실제 연결) · 단계 상태 적기 자리 = 565-1 뒤
- 새 시험 test_564_1_e02_chain.py(차례 · 비율 · 건너뜀 · 끊기 · 실패 격리 · 3D 조각 · 길)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:57:52 +09:00
eomsangdonandClaude Opus 5.5 02a511e400 fix(562-3): 설정 창 D03 노선분석 단축키 글 줄바꿈 고침
- 앞 커밋 b9a342596 은 글 파일이 옛 것으로 남아 제목이 틀림(실제 내용 = 562-3 회전 아이콘 둘 rotateCcw · rotateCw · 설정 ③ D03 노선분석 단축키 줄)
- 그 커밋의 D03 단축키 글이 날 줄바꿈으로 깨져 \n 꼴로 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:56:44 +09:00
eomsangdonandClaude Sonnet 5.5 b9a342596a fix(가이드): B04 모델 표시 옵션 그림 번호 자리 · 글 맞춤(PLAN 540-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 19:55:52 +09:00
eomsangdonandClaude Opus 5.5 6d4964ae55 chore(요구): 요구 392 — 2D 탐색 범위 = 모형 끝까지 · 389 · 390 진행
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:50:46 +09:00
eomsangdonandClaude Opus 5.5 865fb30d7d chore(요구): 요구 390 요구서 · 분석 셋 — 안정성평가 한 번에 해석 · 바로 저장 · 측점 표기 · 보고서 맨 뒤 · 계산 점검
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:35:23 +09:00
eomsangdonandClaude Opus 5.5 00e4275e78 chore(요구): 요구 391 — 요구서 389 답 · 업체 지정 = 조각(연도 · 갈래)마다
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:09:22 +09:00
eomsangdonandClaude Opus 5.5 4dc32fc6cb chore(요구): 요구 390 — 안정성평가 추가 7 항목(해석 한 번에 · 바로 저장 · 측점 표기 · 3D 지형 · 위험 띠 줌 · 보고서 맨 뒤 · 계산 점검)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:08:14 +09:00
eomsangdonandClaude Opus 5.5 7f9919bd59 chore(요구): 요구 389 요구서 · 분석 셋 — 예정노선 생성 칸 걷기 · 기준 지정 두 점 · 위성 · 노선분석 수동 직선 그리기 · 연도 구역
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:56:15 +09:00
eomsangdonandClaude Opus 5.5 d6551bb582 chore(요구): 요구 389 — 예정노선 개선 요구 15 항목(생성 칸 · 기준 지정 · 위성 · 노선분석 수동 직선 그리기 · 연도 구역)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:38:08 +09:00
eomsangdonandClaude Opus 5.5 17982e42eb chore(계획): PLAN 559 이관 — CAD 시험 옛 실패 11 정리 끝(408/408)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:02:46 +09:00
eomsangdonandClaude Sonnet 5.5 354c6b58c6 test(559-2): CAD 시험 설정 경로 — URL.pathname 을 fileURLToPath 로 바꿔 한글 · 띄어쓰기 폴더에서도 @ui 별칭 풀이
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 18:01:57 +09:00
eomsangdonandClaude Opus 5.5 043af8349c test(559-1): CAD 시험 옛 실패 11 정리 — openwebcad npx vitest run 한 번에 tester/cad 시험까지
- 실패 11(259/270) = 코드 흠 0 · 시험 낡음 0 · 지움 0 — 모두 실행 설정 갈래
- 원인: resources/tester/cad 시험을 전용 설정(vitest.tmp.config.ts) 없이 openwebcad 기본 설정으로 돌림(--dir) → 시험 쪽 · src 쪽 flatten-js 가 다른 사본 → Illegal Parameters 10 · distanceTo()[0] undefined 1
- 덤 파일 다섯 로더 오류 = vitest 꼴 아닌 시험(node --experimental-strip-types 넷 통과 · ctrl-shortcut-sheet 는 vitest.ui.config.ts 로 2 통과)
- vitest.config.ts = workspace 셋(openwebcad · tester-cad · tester-cad-ui) — 설정 파일을 그대로 넣으면 root 가 덮여 시험을 못 찾아 extends + root 로 적음
- 결과: npx vitest run 408/408(136 + 270 + 2) · 옛 --dir 명령도 408/408

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:58:51 +09:00
eomsangdonandClaude Opus 5.5 7b94288b6d chore(요구): 요구 388 — CAD 시험 옛 실패 11 정리 · 다른 부분 개선 협의
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:51:58 +09:00
eomsangdonandClaude Opus 5.5 0d3fdc1c17 chore(계획): 요구 387 — 반영됐는데 체크 안 된 항목 점검 · 완료 여섯(335-2 · 336-2 · 395-1 · 406-5 · 460-1 · 472 덧) 이관 · 일부 완료 열하나는 남은 것만
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:37:01 +09:00
eomsangdonandClaude Opus 5.5 e081ecca09 chore(요구): 요구 387 — 계획서 반영됐는데 체크 안 된 항목 점검
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:22:01 +09:00
eomsangdonandClaude Opus 5.5 d5aa0214a6 chore(계획): 요구 386 — 열린 절 안 완료 기록 줄 셋 이관(350-1 아래 검증완료 · 472 브레인 화면 통과 · 492-3 요구 291)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:57:48 +09:00
eomsangdonandClaude Opus 5.5 1f3ed573d9 chore(계획): PLAN 557~558 이관 — 테마 뒤 작은 흠 · 산림청 검증 계정
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:55:12 +09:00
eomsangdon 607aa3e128 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-10-27 10:54:33 +09:00
eomsangdonandClaude Opus 5.5 271c460641 fix(부품·테마): 위험 채움 대비 4.72 · 남은 옛 꼴 → 부품(계산 줄 · 칸 램프 · 넓은 중간 창 · 규칙 꼴 · 구역 머리 · 입력 칸 2열) (PLAN 557-2)
- 위험 채움 토큰 밝음 #e5484d → #d6363c(흰 글 3.91 → 4.72) · 어두움 5.6 · 위험 글 #c0392b 그대로
- 새 부품 계산 줄(ui_template_calc_row) — B08 수리집수면적표 b08-hydro__row · 식 칸 배치를 대신함
- 표 부품 칸 램프(ui-data-table__lamp · --only · ✓) — B08 집계표 · 거리이정표 · B09 설계내역 램프 CSS 세 벌을 걷음(표지 class 는 풍선 · 가이드용으로 남김)
- 모달 넓은 중간 창(wide + width 1100px · 1100 × 680) — B09 운반 창 손 인라인 크기를 걷음
- 값 목록 규칙 꼴(rule) — ☰ 설정 규칙 줄 ul/li 를 걷음
- 접이 구역 머리(createCollapsibleHead = 패널 머리 + 접는 제목) — B08 구조물도 「조합 연결」 제목(옛 b08-sheets__title)을 대신함
- 일반 화면 틀 좁은 폭 · 입력 칸 2열 · 끝 단추 줄 — D01 · E01 이 B02 CSS 를 빌리던 것을 끊음(가이드 장면 선택자 맞춤)
- 부품 모음 견본 · 지킴 시험 test_557_2 · 낡은 시험(198_1 · 247_1 · 251_1 · 257_1 · 276_1 · 551_1)은 부품 CSS · 새 class 로 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:53:34 +09:00
eomsangdon 67e63c2b57 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-27 10:32:16 +09:00
eomsangdonandClaude Opus 5.5 5d28a22832 fix(557-1): B07 · CAD 작은 흠 셋 — 토적도 설정 창 제목 말줄임 · 주석 리본 문자 스타일 칸 겹침 · 넘침 메뉴 왼쪽 판 뒤 가림
- ⚙ 설정 모달 폭 360 → 400 — 「토적도(유토곡선) 설정」 제목 148px 이 124px 칸에 잘리던 것 다 보임(B07 설정 모달만 · 공용 모달 머리 꼴 그대로)
- 주석 › 문자 스타일 칸 다섯 = 다섯 줄(102px)이 리본 높이 66 넘어 묶음 이름과 겹침 → 세 줄(글꼴 / 크기 · 색상 / 굵게 · 기울임) · 묶음 폭 · 다른 묶음 자리 그대로
- 리본(position: fixed)이 쌓임 맥락이라 넘침 메뉴 z 20 이 왼쪽 판(z 3) 밑 → 메뉴 연 동안 리본 z 10
- 지킴 시험 test_557_1_b07_cad_fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:31:39 +09:00
eomsangdonandClaude Sonnet 5.5 c8be1569dd feat(가이드): 산림청 검증 계정 캡처 · B01 산림청 장면 둘 (PLAN 558-1)
- 캡처 도구 account 에 forest(「산림청」 칸) 더함 · 지킴 시험에 forest 더함
- B01 장면 둘(메뉴 · 홈 [예정노선] 칸) 찍음 · 00 설명서 · 01 홈 글에 그림 · 번호 더함

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 10:22:46 +09:00
eomsangdonandClaude Opus 5.5 2d81a57347 chore(요구): 요구 385 — 384 답(작은 흠 고침 · 산림청 계정 · 깃 싱크 · PDF 일원화 · 찾기 · 프로젝트 안 제안)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:13:21 +09:00
eomsangdonandClaude Opus 5.5 4cf025ad48 chore(요구): 요구 384 — 이번 세션 미결 사항 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:02:00 +09:00
eomsangdonandClaude Opus 5.5 45217f42ef chore(계획): PLAN 555~556 이관 — 불러오는 중 · 빌드판 서버 4173 · 페이지 배경 테마
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:07:08 +09:00
eomsangdon 04e964e108 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-27 07:06:56 +09:00
eomsangdonandClaude Sonnet 5.5 806405cd70 test(556-2): 나머지 페이지 CSS 날 흰 바탕 없음 지킴 시험 (PLAN 556-2)
- B07~B09 · D · E · M01 · M02 페이지 CSS 에 날 흰 바탕 금지(E05 종이 미리보기만 예외)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 07:02:59 +09:00
eomsangdon 328b7193df Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-10-27 06:56:49 +09:00
eomsangdonandClaude Opus 5.5 a09ac1c42d feat(모바일): 불러오는 중 판 · 빌드판 서버 · CAD 글꼴 404 (PLAN 555)
- index.html — 첫 페이지 전까지 「불러오는 중」(테마 바탕 · 저장 테마 · 시스템 어두움) · 20 s 뒤 느린 연결 안내 · favicon 요청 없앰
- tools/preview_up.py — 앱 · CAD 빌드 뒤 vite preview(포트 = 개발 포트 − 1000 · /api · /b07-cad 프록시)
- CAD 빌드에 공용 글꼴 복사 — Tailwind 가 url 을 고치지 않아 dist/assets 에서 찾던 404
- 시험 test_555_boot_loader_preview.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:56:26 +09:00
eomsangdon bd321db90f Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-27 06:56:21 +09:00
eomsangdonandClaude Opus 5.5 48db0a8dfe style(B04): 3D 표고 범례 판 · 글 색 = 테마 값 — 어두움에 흰 판 안 남음 (PLAN 556-1 · 요구 383)
- 판 rgba(255,255,255,.9) → --color-surface-raised 90% · 선 --color-border-strong · 그림자 --shadow-sm
- 최고 · 최저 글 → --color-danger · --color-info · 색 띠(그림 값)는 그대로
- 지킴 시험 test_556_1_page_bg 에 범례 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:55:34 +09:00
eomsangdonandClaude Opus 5.5 8b245b16c7 chore(요구): 요구 383 — 블랙 테마 페이지 색도
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:51:12 +09:00
eomsangdonandClaude Opus 5.5 eff165ef1e style(B03): 본문 흰→초록 그러데이션 바탕 걷음 — 틀 페이지 바탕(--color-bg) 위 흰 카드 (PLAN 556-1)
- B04 · B05 · B06 · A00_Common/design 은 틀(c3e83a637)로 바탕 반영 · 카드 · 그림 판 그대로
- 지킴 시험 test_556_1_page_bg 에 B03 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:49:51 +09:00
eomsangdon f97d7a2bd9 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-27 06:48:14 +09:00
eomsangdonandClaude Opus 5.5 c3e83a6375 style(틀): 작업 화면 본문 바탕 = 페이지 바탕(--color-bg) · 흰 바탕 걷음 (PLAN 556-1)
- ui-workflow-layout__main 바탕 --color-canvas → --color-bg(대시보드 #f6f7f9 · 어두움 대응)
- 지킴 시험 test_556_1_page_bg — 작업 · 일반 화면 틀 본문 바탕

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:47:46 +09:00
eomsangdonandClaude Opus 5.5 7df772b545 chore(요구): 요구 382 — 불러오는 중 · 빌드판 서버 · 페이지 배경 테마
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:40:33 +09:00
eomsangdonandClaude Opus 5.5 e37045010a chore(요구): 요구 381 — 모바일 랩탑 5173 빈 화면 · CPU 30% 확인
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:27:07 +09:00
eomsangdonandClaude Opus 5.5 a68fa16854 chore(요구): 요구 380 — 웹 접속 뒤 페이지 내용 없음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:08:40 +09:00
eomsangdon 14f71157d3 auto: 2026-10-27 06:00 (ESD_LAPTOP) 2026-10-27 06:00:06 +09:00
eomsangdonandClaude Opus 5.5 f6c3af3019 chore(계획): PLAN 546~554 이관 — 새 대시보드 · 전역 테마 · 부품 상자 · 페이지 옮김 · 가이드 다시 찍기 끝
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:00:47 +09:00
eomsangdon c19033f236 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-27 00:59:52 +09:00
eomsangdonandClaude Opus 5.5 dba0078f8c fix(B01): 머리 [?] 가 지금 세부 페이지의 가이드 절로 열림 · 사용자 메뉴 「터치 모드」 글자 꺾임 바로잡음 (PLAN 550-1)
- VIEW_HELP(세부 페이지 열쇠 → B01 가이드 절) · 셸 페이지 자리에 data-help-topic
- 터치 모드 이름 · 자동 · 켬 · 끔 단추 nowrap — 메뉴가 넓어져 한 줄
- 지킴 시험 test_550_1 — [?] 절 · B01 링크 · 장면 그림이 파일과 맞음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:59:05 +09:00
eomsangdonandClaude Opus 5.5 5c16da0c5b feat(가이드): B01 대시보드 가이드 새 대시보드 기준으로 다시 씀 — 세부 페이지 글 13 · 그림 23 · 장면 목록 새로 (PLAN 550-1 · 554-1)
- 옛 한 페이지 기준 글 열 편 · 장면 14 폐기 → 세부 페이지마다 한 편(홈 · 프로젝트 목록 · 받은 예정노선 · 임시 보관함 · 사람 · 프로젝트 상세 · 회사 정보 · 회사 설계 기본값 · 시스템 관리 · 사용자 가이드 · 지식 챗봇 · 내 정보)
- 00 설명서에 역할별(일반 · 회사 관리자 · 산림청 · 시스템 관리자) 메뉴 표
- 다른 페이지 글이 가리키는 B01/02 · 03 · 04 · 05 이름 그대로 둠(링크 살림)
- 장면 = #/b01-account?view=<열쇠> · 계정 sys · company_admin · user · 실명 · 연락처 · 사업자번호 · 주소 가림

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:58:59 +09:00
eomsangdon 027297ca1d Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-10-27 00:43:35 +09:00
eomsangdonandClaude Opus 5.5 10fcfc399d chore(guide): E03 셋 · E01_04 가이드 그림 다시 찍음 — 아래 단추 줄 두 줄 감기 반영 (PLAN 552-7 · 554-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:42:55 +09:00
eomsangdon 52771aa699 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-27 00:41:04 +09:00
eomsangdon 59d2824f06 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-27 00:40:37 +09:00
eomsangdon d45fcbb049 Merge remote-tracking branches 'origin/sub_laptop_3' and 'origin/sub_laptop_5' into sub_laptop_1 2026-10-27 00:40:04 +09:00
eomsangdonandClaude Opus 5.5 b1f0617b92 fix(ui_template): 아래 단추 줄 넘침 — 다 안 들면 두 줄로 감음 · 글자 단추 글보다 안 좁아짐 (PLAN 552-7)
- 작업 화면 틀 아래 단추 줄(.ui-sidebar-actions · [data-help-row]) flex-wrap · 글자 단추 min-width max-content
- 넘침(글 잘림 · 줄 밖) 6 → 0 — E03 1600 · D03 · D04 · E03 · E07 860 폭
- 나머지 B03~B09 · D02 · E02 · E04~E06 단추 자리 · 폭 전후 같음
- test_552_4 지킴 한 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:39:34 +09:00
eomsangdonandClaude Sonnet 5.5 9b05c8ebf6 chore(가이드): B09 그림 새 테마로 다시 찍음 — 장면 43 · 실패 0 · 단가연결 모달 불러오기 대기 늘림 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:39:18 +09:00
eomsangdon 0d28d7d4b1 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-27 00:38:51 +09:00
eomsangdonandClaude Sonnet 5.5 e4fc19110c chore(가이드): B05 그림 새 테마로 다시 찍음 · 종단 표 손잡이 · 임시 횡단 누름 자리 장면 고침 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:37:57 +09:00
eomsangdon 9f935073c4 Merge remote-tracking branches 'origin/sub_laptop_1' and 'origin/sub_laptop_3' into main_laptop_1 2026-10-27 00:32:09 +09:00
eomsangdon 98b5dbdedf Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-10-27 00:31:59 +09:00
eomsangdon 2a551ba505 auto: 2026-10-27 00:31 (ESD_LAPTOP) 2026-10-27 00:31:14 +09:00
eomsangdonandClaude Sonnet 5.5 56568cf3a6 chore(guide): E05 가이드 그림 새 화면으로 다시 찍음 — 장면 17 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:31:12 +09:00
eomsangdonandClaude Sonnet 5.5 76f2ed04f0 chore(가이드): B08 그림 새 테마로 다시 찍음 — 장면 15 · 실패 0 · 구조물도 모달 불러오기 대기 늘림 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:29:24 +09:00
eomsangdonandClaude Sonnet 5.5 6069b27b0b chore(가이드): B07 그림 새 테마로 다시 찍음 — 장면 15 · 실패 0 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:27:16 +09:00
eomsangdonandClaude Sonnet 5.5 72ff384bc2 fix(ui_template): 아이콘 단추 누름 1px 이동을 translate 속성으로 — 위치를 transform 으로 잡은 B07 ⚙ 가 누르는 순간 튀어 올라 클릭이 도면 단추로 가던 흠 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:27:15 +09:00
eomsangdonandClaude Sonnet 5.5 695c37136b fix(B04): 시점 단추 글자 세로 꺾임 · 지도 레이어 머리 글 좁게 꺾임 바로잡음 + B04 가이드 그림 새 테마로 다시 찍음 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:25:45 +09:00
eomsangdon 2c1408766e Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_3' and 'origin/sub_laptop_8' into main_laptop_1 2026-10-27 00:24:39 +09:00
eomsangdon c2827bbc44 auto: 2026-10-27 00:23 (ESD_LAPTOP) 2026-10-27 00:23:41 +09:00
eomsangdonandClaude Sonnet 5.5 6a19509bfb chore(guide): E04 가이드 그림 새 화면으로 다시 찍음 — 장면 12 · 선택자 패널 스크롤 칸 · 결과 서랍 열기 단계 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:21:04 +09:00
eomsangdonandClaude Sonnet 5.5 0115202a0a fix(E02): 가이드 그림 새 화면으로 다시 찍음 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:19:09 +09:00
eomsangdonandClaude Sonnet 5.5 c9a27c6b3b chore(가이드): B06 그림 새 테마로 다시 찍음 — 장면 16 · 실패 0 · 아래 종단 판 닫고 찍도록 장면 선택 단계 추가 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:18:07 +09:00
eomsangdonandClaude Sonnet 5.5 6f330974f2 fix(E01): 가이드 그림 새 화면으로 다시 찍음 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:17:59 +09:00
eomsangdonandClaude Sonnet 5.5 2e87363f3b chore(가이드): B02 · B03 그림 새 테마로 다시 찍음 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:17:30 +09:00
eomsangdonandClaude Sonnet 5.5 bcc64b1c2e fix(D04): 가이드 그림 새 화면으로 다시 찍음 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:17:22 +09:00
eomsangdon 0c97b411a3 auto: 2026-10-27 00:16 (ESD_LAPTOP) 2026-10-27 00:16:21 +09:00
eomsangdonandClaude Sonnet 5.5 f4da3dd240 fix(D02): 가이드 그림 새 화면으로 다시 찍음 · 좌표 오류 칸 선택자 is-error (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:16:20 +09:00
eomsangdonandClaude Sonnet 5.5 af0a9a3ab5 fix(D01): 가이드 그림 새 화면으로 다시 찍음 · 새 프로젝트 단추 선택자 고침 (PLAN 554-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:12:34 +09:00
eomsangdon 702d883b1b Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-10-27 00:08:56 +09:00
eomsangdon cfb9f3ad3a Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-27 00:07:39 +09:00
eomsangdonandClaude Opus 5.5 54410dd26a fix(ui-kit): 부품 흠 둘 — 숨김이 부품 display 를 이김 · 모달 머리 [닫기] 한 줄 (PLAN 552-6)
- 부품 CSS 37 파일: display 를 주는 ui-* 클래스마다 같은 파일에 [hidden] { display: none !important } (공용 기본 꼴 없는 곳 · 페이지 꾸밈 위에서도) · 옛 숨김 규칙 일곱 파일 !important 로 · 접이 카드 머리는 collapsible.css
- 모달 머리: 제목 한 줄 말줄임(전체 글 = title) · [닫기] · 머리 단추 flex: none · nowrap
- 부품 모음 모달 견본에 긴 제목 24rem 한 칸
- 지킴 시험 test_552_6_part_fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:07:37 +09:00
eomsangdonandClaude Sonnet 5.5 ffa0821f2d refactor(M02): 흐린 글 span · div · p 26 곳을 보조 글 부품으로 (PLAN 553-8 ①)
- DataFlow 입력 · 출력 · 검증 패널의 옛 __muted 셋(dfip 21 · dfout 2 · dfver 3) = ui-text ui-text--muted (입력 쪽은 글 14)
- 죽은 CSS 셋 걷음 · 지킴 시험 test_553_5 에 한 건 더함

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:06:50 +09:00
eomsangdonandClaude Sonnet 5.5 fb707d5a96 fix(B05·B06): 유리 단추 · [적용] 높이를 단에 맞춤 — 아이콘만 든 유리 단추 그림 감쌈 · 배수 제안 머리 단추 안 눌림 (PLAN 553-8 ②)
- 되돌리기 · 다시하기 · 회전 아이콘을 .ui-btn__icon 으로 싸서 냄: B05 도구줄 32 → 34 · 종단 도구줄 18 → 22 가 단 그대로 32 · 18
- 배수 제안 머리 줄 [적용] · [넣기] · [닫기] flex none · nowrap: 제목이 길면 두 줄로 눌려 22 → 34 가 22 그대로
- 시험 test_553_8_glass_height 새

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 00:06:32 +09:00
eomsangdon e9ff94a0d4 Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1 2026-10-26 23:45:30 +09:00
eomsangdon 82bccd8202 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-26 23:45:29 +09:00
eomsangdonandClaude Opus 5.5 09134bab84 refactor(M02): 띠 알림 · 빈 상태 · 보조 글 · 배지 · 탭 · 표 · 목록 줄 · 도구줄 · 체크를 부품으로 (PLAN 553-5)
- DataFlow: 표 넷 = 데이터표 · 함수 판 알림 · 선/알약 경고 · 끊긴 곳 = 띠 알림 · 알약 · 칸 종류 = 배지 · 고르는 줄 = 목록 줄 · 입력 넷 = 입력 · 도구줄 여섯 · 날 색 → 공통 값
- 구조물 옵션 · 구조물 · 도면 · 면적 · 품목 고르기: 판 띠 = 띠 알림 · 한눈/종류별/부품 = 탭 · 표 둘 = 데이터표 · 체크 둘 = 체크 · 카드 · 번호 = 카드 · 배지 · 도면 도구줄
- 일위대가 · 호표 · 실무자료 · 본 화면 · 서고: 알림 = 띠 알림 · 빈 상태 · 보조 글 · 도구줄 여섯 · 옛 보라 별칭 0
- M01 로직 입력칸 = 입력 부품(작은 칸) · 일위대가 상세 줄 수 고정 시험 693 · 지킴 시험 M02 몫 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:44:19 +09:00
eomsangdonandClaude Opus 5.5 4668642d2c refactor(B05): 좌측 패널 · 곡선 이름표 날 칸을 부품 입력 · 고르기로 (PLAN 553-2)
- 구조물 배치 · 비정규 측점 · 배수 시설 · 설정 칸 17 자리 = ui-input · ui-select(design_structure_form 포함 — M02 도 같은 꼴)
- 페이지 칸 꼴(테두리 · 바탕 · 둥글기 · 여백) 지움 · 높이 = 좌측 패널 양식(26) 그대로 · 고르기 화살표 자리 = 패널 양식 값
- 구조물 조합 이름 칸 = 부품 칸 · 곡선 반지름 · 길이 칸 = 단 24
- 칸 자리 전후 다름 0 · 그림 색 다름 0(밝음 · 어두움)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:41:58 +09:00
eomsangdon 73e5b319ed Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-26 23:39:49 +09:00
eomsangdonandClaude Opus 5.5 877b89f37c fix(ui-kit): 부품 흠 다섯 — 보조 글 줄 안 inline · 단추 실측 = 단 · 숨김 단추 · 놓기 칸 전체 누름 · 격자표 (PLAN 552-5)
- 보조 글: span · small 에 붙은 .ui-text = inline(줄 안 끊김) · p · div 는 한 줄 상자 그대로
- 단추: 위아래 여백 = 1em 기준 + 줄 높이 1 · 왼쪽 패널 양식(.ui-panel-form) 옛 규칙(글 12 · 줄 높이 normal)이 단을 덮지 않음 — B07 도면 단추 35 → 32
- 숨김: .ui-btn · .ui-icon-btn [hidden] = display none(공용 기본 꼴이 안 실린 곳에서도)
- 파일 놓기 칸: 칸 어디든 누름 · 초점 Enter · Space = 파일 고르기(옛 B03 동작) · 단추 누름은 칸으로 올라가 한 번만
- 표: 격자 꼴(grid · 세로선 · 칸 테두리 · 몸 안 이름 칸 th) · 합친 칸(colSpan · rowSpan · head) · columns 비우면 머리 줄 없음 · 기본 꼴 그대로
- 부품 모음 견본: 글 줄 안 보조 글 · 격자표(수량 산출표 꼴)
- 시험: test_552_5_part_fixes 새 · test_552_2_button 높이 식 기대 고침(1em · 줄 높이 1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:38:33 +09:00
eomsangdonandClaude Opus 5.5 cbfe2b8ac2 refactor(B05): 지도 · 노선 추천 범례 셋을 부품 범례로 (PLAN 553-2)
- 평면 상태 글판 범례 · 추천 지도 범례 · 추천 종단 그림 범례 = 부품 「범례」(선 · 점선 표지)
- 표지 색 = 그린 선 값(종단 그림 = --draw-text-secondary · --draw-primary · 지도 = --map-route) — 옛 범례가 계획선을
  --color-primary(초록)로 그려 그림(보라)과 어긋나던 것 맞춤
- 범례 부품 CSS 를 design_page 가 실음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:24:25 +09:00
eomsangdonandClaude Opus 5.5 b309460488 refactor(B05): 노선 추천 창 칸 · 후보 표를 부품 입력 · 표로 (PLAN 553-2)
- 제약 · 비중 · 목표 기울기 칸 = 부품 입력 단 32(옛 작은 칸 30 + 페이지 덮음 둥글기 4 · 글 16 지움) · 지형 고르기 = 단 32
- 후보 표 = 부품 표 데이터표(줄 28 · 수 칸 오른쪽) · 페이지 표 CSS 지움
- 표 부품 CSS 를 design_page 가 실음 · 시험(test_63_4) 머리 칸 글 고침
- 그림 색 전후 다름 0(밝음 · 어두움)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:20:19 +09:00
eomsangdonandClaude Opus 5.5 9f4761a6b0 refactor(B05): 빈 상태 · 안내 글 · 체크를 부품으로 · 날 색 → 공통 값 · 죽은 CSS 지움 (PLAN 553-2)
- 빈 목록 · 빈 유토곡선 = 빈 상태 작은 꼴 · 폼 안내 · 필수 칸 안내 = 보조 글(흐림 · 오류) · 체크 = 부품 체크
  (화면 파일을 시험이 별칭 없이 돌려 클래스로 붙임 · CSS 는 design_page 가 실음)
- 날 색 → 공통 값: 절 · 성토 · 경고 글(rgb 220/37/180) = danger · info · warning · 보라 옛 이름 = accent ·
  #e05b5b = danger · 없는 --border-color(#3a3f4a 로 떨어짐) = --color-border · 떠 있는 판 그림자 · 둥글기 = shadow-lg · radius-cards
- 죽은 CSS(코드 · 시험 0 곳) 지움: route__chip · palette · button-grid · textarea · stale · metrics · drainage__facility-note
- [해제] 단추 안 여백 8 그대로(옆 칸 폭 보호)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:15:19 +09:00
eomsangdon 29a24a85d9 Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_1 2026-10-26 23:08:31 +09:00
eomsangdonandClaude Opus 5.5 e2b3a87828 refactor(B06): 줌 · 조정창 단추 · 머리 알약 · 알림 칩 · 접이 넷 · 띠 알림 · 빈 상태를 부품으로 (PLAN 553-2)
- 줌 [+ − ⤢] = 아이콘 단추 22 · 상세 [칸에 넣기] = 단추 22 · [여럿] = 켜기 칩 22 · 사이 금 지움
- 구조물 조정창 십자 · 9키 · 단 수 [-][+] = 아이콘 단추(선) 22 · ✕ = 고스트 · 옵션 연동 · 경사 = 켜기 칩 24(유입 · 유출 칸 꼴은 그대로)
- 카드 머리 단면유형 = 배지(정보) 20 · 구조물 = 배지(강조) 20
- 알림 칩 = 단추(고스트) 20(색만 경고 / 정보) · 측점 목록 날 단추 = 단추(고스트) 20
- 날 details 넷(변수 위치 안내 · 상세값 · 다른 프로젝트 불러오기 · 횡단 조정) = 접이 구역(ui-collapsible) · 가이드 장면 선택자 `details.` 뗌
- 측점 없는 구조물 줄 · 표준 미반영 알림 = 띠 알림(경고) — 날 색 #fff8e6 · #e0b872 · #7a5a12 · #8a6a22 · 폴백 #d98324 지움
- 빈 그림 자리 = 빈 상태 · 선 치수 글 옛 보라 이름 → --color-accent
- 흉내 DOM 시험 · 서버 Node 묶음이 끄는 파일이라 부품 함수 대신 클래스 · data-size 계약 + 부품 CSS 직접 실음(묶음 셋 빌드 정상)
- 시험 넷 기대 고침 — 108(닫기 클래스) · 179-1(줌 사이 금 규칙 없음) · 196-1(칩 클래스) · 207-1(칩 반경 = 부품)
- 그대로 둔 것 — 수량 산출표(b06-detail__qtable · B07 산출서 병합 칸 · 세로선 = 부품 표에 없는 꼴) · 조정창 고르기 · 암 절토 칸(작은 단 없음) · 조정창 · ⋯ · 측점 목록 떠 있는 판

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:08:13 +09:00
eomsangdon aaf8a237b3 Merge commit '022eff69f' into main_laptop_1 2026-10-26 23:06:07 +09:00
eomsangdonandClaude Sonnet 5.5 917127a63c style(setting_row): 설정 줄 부품에 좁은 꼴 추가 · E06 설정 줄을 부품으로 · D03 3D 창의 B05 클래스 빌림 끊음 (PLAN 553-4 뒤처리)
- 설정 줄 좁은 꼴(compact · 줄 28 · 글 12 · 선 없음) + 부품 모음 견본 + 지킴 시험
- E06 e06-setting 걷음 · 가이드 장면 선택자 갱신
- D03 3D 창 = 자기 클래스(d03-plan__3d-modal · 3d-body) · 모달 부품 그대로 · 가이드 장면 선택자 갱신

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 23:04:33 +09:00
eomsangdonandClaude Opus 5.5 376701d174 test(B05): 배수 시험 가짜 DOM 에 setAttribute — 부품 단 data-size (PLAN 553-2)
- helper_349 · helper_510 가짜 요소가 setAttribute 를 받게 함(baea63804 단추 단 표기)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:59:18 +09:00
eomsangdonandClaude Opus 5.5 022eff69f5 refactor(M01): 표 · 모달 · 띠 알림 · 배지 · 알약 · 보조 글 · 빈 상태를 부품으로 (PLAN 553-5)
- 표 둘 = 표 부품(m01-master__grid = 일반표 · m01-logic__grid = 데이터표) · 페이지 CSS 는 폭 · 머리 고정 · 칸 상태만
- 손 모달 다섯(조달 찾기 · 요소 고르기 · 표 바꾸기 · 복사 시트 · 표 고르기) = 모달 부품 openModal
- 알림 띠 · 막힘 까닭 = 띠 알림 · 태그 = 배지 · 알약 · × · 삭제 · 로직 키 = 단추 부품 · muted 세 벌 = 보조 글 · 빈 줄 = 빈 상태
- M02 가 빌린 M01 클래스(표 · muted · 고르기 창)도 같은 부품으로 · 죽은 CSS 걷음 · 지킴 시험 test_553_5_m01_m02

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:57:33 +09:00
eomsangdon ffa26b2281 Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_4' into sub_laptop_8 2026-10-26 22:52:31 +09:00
eomsangdonandClaude Sonnet 5.5 1595c3503c refactor(B08·B09): 마무리 — 모달 폭 단 960 · 램프 ✓ 같이 · 구조물도 머리 = 패널 머리 · 죽은 CSS 지움 (PLAN 553-3)
- 이정표 모달 폭 = 부품 폭 단 · 성공 램프 = ✓(색만으로 안 가름) · 낡은 시험 문구 맞춤

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:52:05 +09:00
eomsangdonandClaude Opus 5.5 baea638043 refactor(B05): 날 단추 · 작은 도구 단추를 부품 단추 · 아이콘 단추 · 켜기 칩 · 목록 줄로 (PLAN 553-2)
- 종단 도구 · [창 분할] = 단추 유리 · 보조 단 18 · ▲▼ = 아이콘 단추 선 18 · 배율 + − ⤢ = 아이콘 단추 22
- 배수 머리 단추 = 단추 22 · 층 단추 = 켜기 칩 20(색 점 = 층 색) · 유역 줄 = 목록 줄 32
- [추가] · [삭제] · [리셋] · [해제] · 확폭 = 단추 주 · 위험 고스트 · 보조 · [-][+] = 아이콘 단추 24
- 지도 도구줄 · 3D 보기 줄 = 단 32(34 에서) · 접기 ▾ = 아이콘 단추 18 · 잰 값 닫기 = 아이콘 단추 24
- 페이지 CSS 의 단추 꼴(테두리 · 바탕 · 글 · 둥글기) 지움 · 날 색 대체값(#d98a00 · #5b8def · #3a3f4a) 지움
- 시험 선택자 · 가짜 DOM(setAttribute) 같이 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:51:58 +09:00
eomsangdon bb85bc26ba Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-26 22:51:50 +09:00
eomsangdon a708bdf7a2 Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-10-26 22:48:34 +09:00
eomsangdonandClaude Opus 5.5 815aa464cc fix(cad): 부품 연결 보강 — DOM 없는 시험에선 기본 꼴 주입 건너뜀 · 목록 줄 CSS 실음 · 블록 판 단추 줄 넘김 · 지킴 시험 (PLAN 553-6)
- integration/ui-kit.ts: document 없을 때 injectBaseStyles 안 부름(vitest ime-sink 깨짐) · ui_template_list_row.css 실음(블록 이름 줄 = ui-sidebar-row)
- 블록 라이브러리 [파일로 내보내기] [파일에서 가져오기] = 글 12 로 한 줄에 둘이 안 들어가 줄을 넘김(글 자르지 않음)
- resources/tester/test_553_6_cad_parts.py: 부품 클래스 · 확인창(window.confirm · prompt 0) · 부품 CSS 먼저 · 날 색 · 보라 별칭 · 옛 자체 단추 규칙 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:46:03 +09:00
eomsangdonandClaude Sonnet 5.5 6d8abe685d refactor(E03·E04·E07): 지도 값 풍선을 툴팁 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:40:30 +09:00
eomsangdon 3cf5ebc887 Merge remote-tracking branches 'origin/sub_laptop_3', 'origin/sub_laptop_4', 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into main_laptop_1 2026-10-26 22:39:23 +09:00
eomsangdon 9d9b57d089 Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1 2026-10-26 22:38:02 +09:00
eomsangdonandClaude Sonnet 5.5 e761dda222 refactor(E07): 보기 고르기 · 범례 판 · 요약 값 목록 · 등급 표 · 측점 표 · 진행 막대 · 띠 알림을 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:37:53 +09:00
eomsangdonandClaude Opus 5.5 45d7cddc34 Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_3
CAD 겉 겹침 = sub3 판 기준 · sub8 의 문서 탭 × · + 아이콘 단추 부품 · test_cad_host_tabs 고침 · 도각 편집 글 크기 살림 · main.tsx 의 injectBaseStyles 겹침 걷음(ui-kit 한 곳)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:37:45 +09:00
eomsangdonandClaude Opus 5.5 913122ac4e refactor(B06): 카드 설계 줄 · 아래 띠 단추를 부품으로 — 분할 고르기 · 켜기 칩 · 단추 · 아이콘 단추 (PLAN 553-2)
- 측구 · 측구형식 · 경사 방향 묶음 = 부품 분할 고르기 20 · 토사 · 측구 · 단경사 켜고 끄기 = 켜기 칩 20 · [상세] = 단추(고스트) 20
- ⋯ 넘침 펼침 = 아이콘 단추(선) 22 꼴(펼침 = aria-pressed) · 암 경계 ▲▼↺ · 암 절토 ↺ = 아이콘 단추 22
- 지운 자체 CSS — b06-design__btn(--active) · 펼침 보라 채움 · rockb 단추 꼴 · 사이 금 · 글 0.72rem → --text-caption
- 부품 아이콘 단추의 display 가 hidden 을 덮어 선 칸 숨김 단추는 페이지에 숨김 한 줄
- 카드 높이 7 px 줄음(설계 줄 단추 24 → 20 · 지반 칩 24 → 20) · 그림 색 전후 같음(34 장면 × 102~120 선)
- 시험 헬퍼 둘 — helper_318 = 단추 줄 시험이 진짜 부품 파일을 흉내 노드로 실음 · helper_472 = 켜기 칩 글을 안쪽 span 에서 읽음
- Line_Cell · CutSlope 는 흉내 DOM 시험(370-3 등)이 @ui 를 안 풀어 부품 클래스 · data-size 계약으로 붙임

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:36:33 +09:00
eomsangdonandClaude Opus 5.5 de250ab759 refactor(cad): CAD 앱 겉 자체 변종을 부품 상자 부품으로 — 상태막대 켜기 칩 · 패널 탭 · 떠 있는 판 · 메뉴 · 아이콘 단추 · 확인창 (PLAN 553-6)
- 부품 상자 CSS · 단추 기본 꼴을 CAD iframe 에 실음(integration/ui-kit.ts) · 자리 · 폭은 cad-* 그대로
- 상태막대 = ui-toggle--chip 22 · 특성/레이어 = ui-tabs 36 · ↶↷ · 리본 접기 · 탐색 막대 = ui-icon-btn 20 · 32
- 빠른 특성 · 블록 라이브러리 · 자리표 = ui-float-panel · 리본 넘침 · 스냅 · 명령 자동완성 = ui-ctx-menu
- window.confirm · window.prompt → askConfirm · askText · 빈 안내 = ui-empty 작은 꼴
- 높이 단: 리본 칸 19→18 · 명령 칸 26→28 · 레이어 줄 40→42 · 문서 탭 25→24 · 도각 편집 단추 24
- 마우스 바탕 = --color-hover · 켜짐 = --color-accent-soft · 도면 그림 색(--draw-*) 안 건드림
- 안 쓰는 DropdownButton 지움(쓰는 곳 0 · grep)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:35:43 +09:00
eomsangdonandClaude Sonnet 5.5 bb6c67deab refactor(E06): 절리 표 · 측점 목록 · 모드 표 · 판정 램프 · 보기 고르기 · 범례 · 갱신 띠를 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:34:36 +09:00
eomsangdonandClaude Sonnet 5.5 97fc00fafb refactor(A00): ☰ 설정 창 · 서랍 터치 고르기 → 부품 상자 — 보조 탭 · 라디오 · 키 칩(배지) · CAD 별칭 표 · 분할 고르기 (PLAN 553-3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:34:04 +09:00
eomsangdonandClaude Opus 5.5 b8a19e983f style(cad): CAD 앱 겉을 앱 공통 값 · 크기 단 · 확인창 부품으로 — window.confirm · prompt 걷음 (PLAN 553-6)
- 블록 이름 묻기 = 부품 글 묻기(askText) · 블록 지우기 = 위험 확인(showConfirmDialog) — CAD 창 안에 앱과 같은 확인창 꼴(injectBaseStyles)
- 크기 단 — 리본 특성 칸 19 → 18 · 빠른 실행 24×20 → 20 · 상태막대 단추 21 → 22 · 명령 칸 26 → 28 · 왼쪽 판 탭 38 → 36 · Button 40 → 42
  · 블록 · 자리표 단추 22 · 24 · 큰 리본 도구 66 · 리본 탭 줄은 꼴 그대로
- 마우스 바탕 = --color-hover · 켜짐 바탕 = --color-accent-soft(옛 보라 별칭 걷음) · 떠 있는 판 · 메뉴 둥글기 = 공통 값(날 2 · 3 · 5 px 11 곳 → 0)
- 안 쓰는 DropdownButton 지움(grep 0) · 그림(캔버스 바탕 · --draw-*) 전후 같음 · B07 전후 촬영 CAD 안 배치 바뀜 = 빠른 실행 칸만

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:33:45 +09:00
eomsangdonandClaude Sonnet 5.5 361ce51659 refactor(B08·B09): 자체 변종 → 부품 상자 — 탭 줄 · 분할 고르기 · 배지 칩 · 도구줄 · 떠 있는 판 · 보조 글 (PLAN 553-3)
- 위 · 보조 탭 줄 = 부품 탭(fillTabs · 밑줄 · 알약) · B09 좌측 스위치 = 분할 고르기
- 연결 칩 · 검사 탭 이름 · 끌기 태그 = 배지 · 이정표 도구 · 머리 줄 · 저장 줄 = 도구줄 · 구간 고침 · 행 고름 창 = 떠 있는 판
- 안내 · 빈 줄 · 상태 글 = 보조 글 · 운반 칸 = 카드 · 인쇄 미리보기 좌측 줄 = B09 dock 한 벌 · 쪽 체크 = 라디오 · 체크 부품
- 시험 흉내 틀 · 문구 맞춤

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:33:35 +09:00
eomsangdon 1acd6fb5d2 Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_1 2026-10-26 22:29:36 +09:00
eomsangdon 2ea5b39fdf Merge remote-tracking branch 'origin/dev' into sub_laptop_1 2026-10-26 22:29:33 +09:00
eomsangdonandClaude Sonnet 5.5 835f9d9290 refactor(B07): 상세 설계 화면 페이지 자체 변종을 부품 상자 부품으로 — 도면 단추 · 장 번호 · 도구 단추 · 카드 · 띠 알림 · 값 목록 (PLAN 553-1)
- 도면 단추(날 ui-panel-btn) · 장 번호 · 도구 단추 = 단추 부품 · ⚙ = 아이콘 단추 꼴 · 평면도 줄 = 목록 줄 꼴
- 머리 = 패널 머리 + 배지 · 확정 건너뜀 = 띠 알림 · 빈 글 = 빈 상태 · 측점 정보 = 값 목록 · ⚙ 모달 그룹 상자 = 카드 · 종류 이름 칸 = 입력
- window.confirm 둘 → 확인창 부품 · 죽은 CSS 걷음 · 시험 헬퍼 부품 가짜 한 벌(helper_b07_part_stubs) · 153-1 옛 오류(removeAttribute) 고침 · 가이드 선택자 · 지킴 시험

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:29:22 +09:00
eomsangdonandClaude Sonnet 5.5 cb6f9e4d4c fix(E05): 보고서 없음 안내도 빈 자리 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:29:05 +09:00
eomsangdonandClaude Sonnet 5.5 ac24415327 refactor(E05): 갱신 띠 · PDF 미리보기 창 · 안내 글 · 빈 자리를 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:28:30 +09:00
eomsangdon a76ec47cb4 Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_8' into main_laptop_1 2026-10-26 22:26:59 +09:00
eomsangdonandClaude Sonnet 5.5 3e4fcd8845 refactor(E04): 보기 고르기 · 구간 줄 · 결과 값 목록 · 범례 판 · 진행 막대 · 띠 알림을 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:22:46 +09:00
eomsangdonandClaude Sonnet 5.5 57ec6d247b refactor(E03): 표 · 카드 · 보조 글 · 빈 자리 · 체크 · 범례 · 판정 램프 · 별 단추 · 값 목록 · 3D 창을 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:19:10 +09:00
eomsangdonandClaude Opus 5.5 a5bde66bd3 style(cad_host): CAD 탭 × · + 를 아이콘 단추 꼴로 · 도각 편집 단추 24 · 날 그림자 · 보라 별칭 → 공통 값 (PLAN 553-6)
- 탭 줄 × = 아이콘 단추(18) · + = 아이콘 단추(22) — 문서 탭 꼴은 그대로 · 자체 단추 CSS 지움
- 도각 편집 띠 [완료 · 파일 불러오기 · 기본 도각 복원 · 취소] = 부품 단추 24(자체 여백 · 글 크기 지움)
- 불러오는 중 상자 그림자 = --shadow-lg · 오류 색 날 값 지움 · 시트 표(ui-sheet) 켬 색 · 안내 글 = 공통 값
- 시험 test_cad_host_tabs 가 클래스 여럿 붙은 단추를 찾게 고침 · B07 전후 촬영 CAD 안 배치 0 바뀜 · 바탕 색 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:17:44 +09:00
eomsangdonandClaude Sonnet 5.5 6bb0a5312d refactor(B03): 파일 입력 화면 마무리 — 파일 놓기 칸 · 모달 · 확인창 · 고르는 카드 · 목록 줄 부품 (PLAN 553-1)
- 놓기 칸 = 파일 놓기 칸 부품(한 줄로 눕힘) · 임시 보관함 창 = 모달 + 고르는 카드 + 빈 상태 · 교체 확인 = 확인창
- 업로드 도중 window.confirm 둘 → 확인창 부품(글 그대로 · 단추 이름으로 선택 안내) · 예정노선 고름 줄 = 목록 줄
- 모달 CSS · 임시 목록 옛 CSS 걷음 · 가이드 촬영 선택자 · 지킴 시험

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:13:40 +09:00
eomsangdonandClaude Opus 5.5 b037073b10 style(spreadsheet): 셀 서식 · 메모 · 찾기 · 패널 창 · 오류 ⚠ 를 부품 모달 · 탭 · 떠 있는 판 · 단추로 (PLAN 553-6)
- 셀 서식 창 = 부품 모달(40rem) · 탭(32) · 칸 = 부품 입력 · 고르기 28 · 자체 판 · 탭 · 칸 CSS 지움
- 메모 편집 = 공용 확인창 판(.ui-confirm) · 글 칸 = 부품 입력
- 찾기 · 바꾸기 = 떠 있는 판(머리 없음) · 칸 24 · 단추 22 · 닫기 = 아이콘 단추
- 데이터 패널 창(조건부 서식 · 유효성 · 이름) = 떠 있는 판(머리 · 닫기 ✕)
- 오류 ⚠ = 아이콘 단추(선 18) · 골라 붙여넣기 · 정렬 칸 · 알약 찾기 칸 = 부품 단추 · 고르기 · 입력
- 옛 보라 별칭 · 날 검정 가림막 → 공통 값(hover · accent · scrim) · 창 닫힘 감시가 모달도 기다림
- 날 단추 23 → 15(남음 = 수식 줄 칸 단추 · 격자 안 ▼ · 알약) · 자체 꼴 규칙 95 → 77 · 격자 색 전후 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:11:14 +09:00
eomsangdon dbff07a4f8 Merge remote-tracking branch 'origin/dev' into sub_laptop_1 2026-10-26 22:09:53 +09:00
eomsangdonandClaude Sonnet 5.5 58dafb1572 refactor(B04): 전처리 화면 자체 변종을 부품 상자 부품으로 — 켜기 칩 · 분할 고르기 · 고르기 · 슬라이더 · 값 목록 · 카드 · 도구줄 (PLAN 553-1)
- 지도 층 단추 열다섯 안팎 · 표시 토글 셋 · 라이다 겹침 = 켜기 칩(층 색 점) · 도엽 보간 방식 = 분할 고르기 · 도구줄 부품
- 고르기 38 → 고르기 부품 · 점 크기 · 밀도 = 슬라이더 · 입력 파일 정보 = 값 목록 · 초기화 = 위험 고스트 · 묶음 상자 = 카드 + 패널 머리
- 죽은 CSS 열여섯 걷음 · 날 색 → 공통 값 · 가이드 촬영 선택자 · 지킴 시험 test_553_1_pages 늘림

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:09:36 +09:00
eomsangdon 12fda57346 Merge remote-tracking branches 'origin/sub_laptop_5' and 'origin/sub_laptop_6' into sub_laptop_2 2026-10-26 22:07:17 +09:00
eomsangdonandClaude Sonnet 5.5 eaf5079736 refactor(E02): 지반 표 넷을 표 부품으로 · 칸 이름 줄을 입력 부품 꼴로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:04:38 +09:00
eomsangdonandClaude Sonnet 5.5 0a3bab1223 refactor(D04): 업체 표를 표 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:02:54 +09:00
eomsangdonandClaude Opus 5.5 cb02b1f152 test(552·B06): 시험 둘 고침 — 부품 모음 견본 글 「더하기」 → 「추가」 · 다단 성토부선 시험이 logic 으로 옮긴 B06 관 모양 파일을 읽음
- ui_template_ui_kit_parts.ts 아이콘 단추 견본 title 「더하기」가 보이는 글 금지어(PLAN 30-1)에 걸림 → 「추가」
- test_user_values_survive.py 다단 성토부선 시험 = 옛 B06_Section_UI_Cross_Culvert_{Geom,Tiers}.ts 경로 · e4befd158(518-2)에서 B06_Section/logic/B06_Section_Cross_Culvert_{Geom,Tiers}.ts 로 옮겨 감 · 판정 글은 그대로 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:02:15 +09:00
eomsangdon 4ca4c2717a Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1 2026-10-26 22:02:08 +09:00
eomsangdon b2d8f10cda Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-26 22:02:06 +09:00
eomsangdon cce69eb579 Merge remote-tracking branch 'origin/dev' into sub_laptop_5 2026-10-26 22:01:31 +09:00
eomsangdon dd488eea4a Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_6 2026-10-26 22:01:31 +09:00
eomsangdonandClaude Sonnet 5.5 91e3dc4141 refactor(E01·E02): 빠진 횡단 띠 · 지반 카드 · 입력 · 고르기 · 안내 글 · 상태 글을 부품으로 (PLAN 553-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:01:22 +09:00
eomsangdon 64fd45b8d4 Merge remote-tracking branch 'origin/dev' into sub_laptop_8 2026-10-26 22:01:00 +09:00
eomsangdonandClaude Opus 5.5 2033125fec style(spreadsheet): 도구줄 단추 · 칸 · 시트 탭 [+] 를 부품 단추 24 · 입력 · 고르기 28 · 아이콘 단추로 (PLAN 553-6)
- 도구줄 단추 23 → 부품 단추 24 · 글꼴 · 크기 · 표시 형식 칸 → 부품 입력 28 · 정렬 고르기 → 부품 고르기 28
- 색 지우기 ✕ → 아이콘 단추 18 · 시트 탭 [+] → 아이콘 단추(선) 22 · 켬(is-on) 색 = 공통 값(옛 보라 별칭 걷음)
- 메모 창 뒷막 = --color-scrim · 자체 단추 · 칸 CSS 지움(폭만 남김)
- 격자 · 시트 탭 꼴 · 배치 그대로 — 도구줄 안 단추만 3 px 넓어짐(줄바꿈 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:00:37 +09:00
eomsangdon 4cd201aa35 Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1 2026-10-26 22:00:17 +09:00
eomsangdon b114616505 Merge remote-tracking branch 'origin/dev' into sub_laptop_4 2026-10-26 22:00:02 +09:00
eomsangdonandClaude Sonnet 5.5 84f9547f91 feat(A00): 페이지 탭 줄 채움 도우미 — 부품 탭을 줄 요소 안에 채움 (PLAN 553-3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 21:59:50 +09:00
eomsangdon 6d8b38b518 Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_3' and 'origin/sub_laptop_5' into main_laptop_1 2026-10-26 21:58:45 +09:00
eomsangdonandClaude Opus 5.5 6008ba654e merge(sub_laptop_6): 552-3 묶음 부품 15 — 부품 모음 목록 import 충돌 양쪽 살림(552-2 기본 import + 552-3 부품 파일 import)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:58:15 +09:00
eomsangdon f54eed25bc refactor(D03·D04): 진행 막대 · 고르는 카드 · 입력 · 고르기 · 체크 · 띠 알림을 부품으로 (PLAN 553-4) 2026-10-26 21:57:01 +09:00
eomsangdonandClaude Sonnet 5.5 25adc2f0e7 refactor(공개화면): A01~A09 · B10 · B11 을 공개 화면 틀 · 카드 · 알림 · 배지 · 체크 · 접이 부품으로 (PLAN 553-7)
- A01~A05 hero 다섯 벌 · 구역 제목 복제 CSS 걷고 공개 화면 틀 한 벌(wide 옵션 · 구역 제목 가운데 · 머리 아랫줄은 자리 안 먹는 그림자)
- 카드 = 카드 부품(로그인 · 가입 · 지원 · 약관 · 기능 카드 · 소식 줄 · B11 단계 카드) · 지원 성공 · B10 안내 = 띠 알림 · 태그 = 배지
- A07 약관 날 체크 · 날 details = 체크 + 접이 부품 · A08 날 textarea = 입력 여러 줄 · 빈 소식 = 빈 상태
- 죽은 A07_Register_UI_Page · 쓰이지 않는 CSS 걷음 · 띠 알림 hidden 받음
- 지킴 시험 test_553_7_public_pages

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 21:56:42 +09:00
eomsangdonandClaude Opus 5.5 47b396b53d feat(ui-kit): 묶음 부품 다섯 — 표 · ⋯ 메뉴 · 모달 · 확인창 · 설정 줄 · 파일 놓기 칸 (PLAN 552-3)
- 새 부품 파일: ui_template_table · setting_row · drop · createRowMenu(context_menu) · 모달 폭 단 · askConfirm · askText(window.prompt 대신) · confirmPanel
- 단추 = 552-2 단추 부품(⋯ 아이콘 단추 28 · 띠 알림 닫기 20 · 놓기 칸 [파일 고르기] 28)
- 대시보드 PageKit 의 dataTable · tableShell · tableRow · rowMenu · settingRow · settingCard · valueText = 부품을 다시 내보냄(한 벌 · 부름 꼴 그대로) · B01 옛 CSS 걷음
- showConfirmDialog 는 모달 부품으로 옮기고 본체가 다시 내보냄 · 모달 가림막 옛 보라 → --color-scrim
- 시험: test_552_3_* 다섯 더함 · 옮긴 함수 자리를 읽던 test_290_1 · test_505_2 고침 · 부품 CSS import 를 못 읽던 시험 도우미 넷(130 · 153 · 280_5 · 311)이 .css 를 건너뜀
- 전후 촬영: B01 여덟 화면 자리 같음 · ⋯ 아이콘만 1 px 왼쪽(단추 부품 가운데 맞춤)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:55:11 +09:00
eomsangdonandClaude Sonnet 5.5 a826804277 refactor(B02·B03): 페이지 자체 변종을 부품 상자 부품으로 — 파일 카드 · 띠 알림 · 진행 막대 · 켜기 · 패널 머리 · 단추 (PLAN 553-1)
- B03 파일 카드 = 카드 부품 · [선택] · [×] = 단추 · 아이콘 단추 · 진행 막대 · 오류 띠 = 부품
- B03 현황 · 이어올리기 · 예정노선 바뀜 = 띠 알림 · LAS 없이 설계 = 켜기 · 노선 · 지형 제목 = 패널 머리
- B03 · B02 죽은 CSS 걷음 · 날 색 → 공통 값 · 가이드 촬영 선택자 · 지킴 시험 test_553_1_pages

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 21:53:32 +09:00
eomsangdon 065ca8b983 refactor(D01·D02): 날 입력 · 안내 글을 부품으로 — 측점 표기 · 연도 지점 칸 · 안내 줄 (PLAN 553-4) 2026-10-26 21:51:18 +09:00
eomsangdonandClaude Opus 5.5 9729892a3d merge(sub_laptop_2): 552-2 기본 부품 13 — 부품 모음 목록 충돌 양쪽 살림(552-2 견본 + 552-3 · 552-4 묶음)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:41:46 +09:00
eomsangdonandClaude Opus 5.5 2c51bb53a6 feat(ui-kit): 기본 부품 입력 · 고르기 · 슬라이더 · 체크 · 켜기 · 분할 · 탭 · 배지 · 툴팁 · 보조 글 (PLAN 552-2)
- ui_template_input.ts · .css 새로 — createInputField · createSelectField 를 옮김(ui_template_elements 가 다시 내보냄 · 부르는 길 그대로) · size(data-size 단 높이) · unit(칸 안 단위) · multiline · createSlider(이름 | 막대 | 값 글 3열)
- 옛 compact 는 단을 안 붙임 — 옛 작은 칸 30 그대로(32 로 맞추면 페이지 칸이 바뀜 · 553 에서 단으로 옮김)
- 입력 · 고르기 상태 = 마우스 선 짙게 · 막힘 흐림 · 오류 빨강(aria-invalid 같이) · 고르기 화살표 = 보라 → 보조 글 색(밝음 · 어두움 따로)
- 체크 = 라디오 · 칩 꼴 더함(createCheckbox 부름 그대로) · 켜기(스위치 · 색 점 칩) · 분할 고르기(대시보드 .b01-sys-seg 꼴) · 탭(밑줄 · 보조 알약 = 옛 .ui-subtabs · 수 배지)
- 배지(알약 · 수 · 램프 ✓ ✗ · 점 · 옛 .ui-tag 같이) · 툴팁 제자리 상자(tooltipBox) · 보조 글(흐린 · 안내 · 오류 ✗ · 경고 ! · 성공 ✓)
- 부품 모음 기본 묶음 13 줄 모두 새 부품 견본 · 상태 다섯 칸 · 여덟 단 한 줄
- 시험 test_552_2_parts 새로(머리 · 옛 이름 · 크기 단 · 상태 · 날 색 없음 · 부품 모음 13)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:39:26 +09:00
eomsangdon 66dcbdad17 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-26 21:34:56 +09:00
eomsangdonandClaude Opus 5.5 a3365946fd feat(ui-kit): 틀 부품 셋 — 도구줄 · 떠 있는 판 · 오른쪽 서랍 겉 (PLAN 552-4)
- 도구줄 createToolbar: data-size 24 · 28 · 32 · 나눔선 · 밀기 · 유리 꼴 · 단 없는 단추에 같은 단을 붙임(552-2 setUiSize)
- 떠 있는 판 createFloatPanel: 머리 32 · 닫기 = 아이콘 단추 24(x) · 끌기 머리 · 팝오버
- 오른쪽 서랍: 흰 판 + 1 px 선 · 자리 · 폭 · 열고 닫기 그대로 · 머리 규칙 8
- 부품 모음 틀 아홉 견본 다 참 · 지킴 시험 아홉 부품

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:33:32 +09:00
eomsangdon 8cf6a7b626 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-10-26 21:30:25 +09:00
eomsangdonandClaude Opus 5.5 ed6554a977 merge(dev): 552-2 단추 · 552-4 틀 받음 — 부품 모음 목록 = 양쪽 살림(묶음 열다섯 · 틀 아홉) · emptyState = 빈 상태 부품 · 아이콘 = ui_template_icons
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:29:58 +09:00
eomsangdon c54afddfe8 Merge remote-tracking branch 'origin/dev' into main_laptop_1 2026-10-26 21:29:09 +09:00
eomsangdon 894ea5059e Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-26 21:28:43 +09:00
eomsangdon 6271164dd5 Merge remote-tracking branch 'origin/dev' into sub_laptop_3 2026-10-26 21:28:30 +09:00
eomsangdonandClaude Opus 5.5 7e584c1e7b feat(ui-kit): 묶음 부품 열 — 카드 · 접이 카드 · 빈 상태 · 알림 · 띠 알림 · 숫자 카드 · 값 목록 · 진행 막대 · 작은 그래프 · 범례 (PLAN 552-3)
- 새 부품 파일: ui_template_card · empty · banner · stat · kv · toast · flow_legend(.ts) · 진행 막대 · 막대 · 원 그래프 덧붙임
- createCard · showToast · 로딩 가림막은 부품 파일로 옮기고 본체(ui_template_elements)가 다시 내보냄 — 부름 꼴 그대로
- 대시보드 emptyState = 빈 상태 부품을 씀(한 벌) · 선 카드 그림자 0 · 가림막 옛 보라 → --color-scrim
- 부품 모음 화면 견본(밝음 · 어두움 · 상태) · 지킴 시험 test_552_3_* 열
- 전후 촬영: B01 여덟 화면 자리 · 화소 같음(실시간 CPU 값만 다름) · A01 · B08 카드 그림자만 다름

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:28:29 +09:00
eomsangdonandClaude Opus 5.5 f941ddc3f5 feat(ui-kit): 틀 부품 여섯 — 작업 화면 틀 겉 · 일반 화면 틀 · 공개 화면 틀 · 단계 줄 · 패널 머리 · 목록 줄 (PLAN 552-4)
- 작업 화면 틀(overlay): 왼쪽 패널 = 흰 판 + 1 px 선 · 그림자 없음 · 덮개 = --color-scrim · 손잡이 강조색 그대로 · 자리 · 폭 그대로
- 일반 화면 틀 createPageFrame(대시보드 세부 꼴) · 공개 화면 틀 createPublicFrame(A01 hero · section 꼴)
- 단계 줄 data-size 28 · 40 · 상태 = 공통 값 · 패널 머리 createPanelHead · 목록 줄 createListRow(옛 .ui-sidebar-row 겉 함께)
- 부품 모음: 떠 있는 판 · 서랍 · 도구줄 · 단계 줄 · 패널 머리 · 목록 줄 여섯 줄을 frame 묶음으로 옮김 · 견본 = ui_template_ui_kit_frames.ts
- 지킴 시험 test_552_4_frames.py · B02~B09 · D · E · M02 전후 자리 수치 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:28:17 +09:00
eomsangdonandClaude Opus 5.5 3ccb96fbdb feat(ui-kit): 부품 단추 · 아이콘 단추 · 아이콘 · 크기 여덟 단 (PLAN 552-2)
- ui_template_button.ts · .css 새로 — createButton 을 옮김(ui_template_elements 가 다시 내보냄 · 부르는 길 그대로) · createIconButton 새로(고스트 · 선 · 채움 · aria-pressed · 글자 기호 · title + aria-label)
- 크기 = data-size 18 · 20 · 22 · 24 · 28 · 32 · 36 · 42 · 단마다 높이 · 글자 · 여백 · 아이콘 · 둥글기 값(--ui-size-*) · 입력 · 고르기 · 분할 · 탭이 같은 값을 읽음
- 단추 높이 = 글 한 줄 + 여백 + 테두리(높이를 박지 않음) — 크기를 안 준 옛 단추 · 페이지 여백 덮어쓰기 그대로 · 옛 size "sm" = 18 단(같은 꼴) · 글 링크 꼴 더함
- 상태 = 마우스 · 눌림을 data-state 로도 받음(부품 모음이 굳혀 보임) · 마우스 색 = --color-primary-hover · --color-accent-soft(옛 보라 별칭 대신) · 눌림 = 한 칸 내려앉음
- 아이콘 = Tabler 32 더함(지도 · 길 · 자 · 합 · 크레인 · 핀 · 나침반 · 설정 · 닫기 · 메뉴 · 되돌리기 …) + PageKit 여섯을 옮김 · .ui-icon 규칙 전역(ui_template_icons.css)
- 부품 모음 = 단추 · 아이콘 단추 · 아이콘 견본 · 부품마다 여덟 단 한 줄
- 시험 test_552_2_button 새로(단 값 · 높이 식 · 옛 이름 · 상태 · 날 색 없음 · 아이콘)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:26:01 +09:00
eomsangdon e673714748 Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1 2026-10-26 21:08:44 +09:00
eomsangdonandClaude Opus 5.5 aeb6bc498f feat(테마): 공통 값 한 벌 · Noto Sans KR 글꼴 파일 실음 (551-1)
- ui_template_theme.css = 대시보드 값(초록 강조 #1f7a4d · 바탕 #f6f7f9 · 선 #e6e8ec · 글 #1f2328 · 둥글기 10) 밝음 · 어두움 두 블록 · 시스템 어두움 = 같은 값
- 옛 보라 이름(deep-iris · plum-velvet · royal-amethyst · mist-violet · lavender-wash) = 새 값의 별칭 · 안 쓰는 그라디언트 3 · twilight-beam · shadow-feature 지움
- 새 토큰 accent-soft · hover · surface-sunken · border-strong · primary-hover · danger-fill(#e5484d 채운 바탕) · scrim · 위험 글자 = #c0392b 그대로
- 글꼴 = --font-ui 한 토큰(--font-body · --font-display 별칭) · Inter · Britti 등 앞 이름 뺌 · ui_template/fonts/ 에 Noto Sans KR 가변 굵기 woff2 39 묶음(unicode-range) + OFL.txt · 첫 화면 = 196 KB 한 파일
- 대시보드 --b01-* = 공통 값의 별칭 · 셸 덮어쓰기 10 줄 · 셸 어두움 블록 지움(판 바탕 흰색 한 줄만 남김)
- test_546: 셸 hex 단정 → theme.css 값 · 셸 별칭 단정으로(값 자리가 theme.css 로 옮겨 감) · test_551_1 새 지킴 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:08:01 +09:00
eomsangdonandClaude Opus 5.5 edd06e4b51 chore(요구): 552-1 부품 목록 37 · 페이지별 변종 수 · 옛 변종 → 새 부품 짝 표 · 높이 → 여덟 단 짝 표
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:01:49 +09:00
eomsangdon 4348a47100 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 2026-10-26 21:01:11 +09:00
eomsangdonandClaude Opus 5.5 c03228778b feat(ui-kit): 부품 목록 37 확정 — 조사로 찾은 일곱 더함 · 크기 단 짝 규칙 · 테마 칸 안 스크롤 막대 없앰 (PLAN 552-1)
- 더함: 슬라이더 · 보조 글 · 떠 있는 판 · 목록 줄 · 띠 알림 · 값 목록 · 범례 (기본 13 · 묶음 21 · 틀 3)
- 규칙 머리: 옛 size sm = 18 · compact = 32 · 지금 높이는 가까운 단(같은 거리면 표 · 도구 = 작은 단)
- iframe content-box — 테두리 2 px 때문에 생기던 안 스크롤 막대 없앰

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:00:24 +09:00
eomsangdon f6643fb946 Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1 2026-10-26 20:59:49 +09:00
eomsangdonandClaude Opus 5.5 7e07da4b31 feat(테마): 그림 전용 값 --draw-* — 종단 · 횡단 · 이정표 SVG · 지도 계획선 · CAD 바탕 · 3D 바탕 색을 화면 테마와 떼어 지금 색 고정 (PLAN 551-2)
- ui_template_theme.css 끝 블록 = --draw-* 17 개 · 밝음 / 어두움 / 시스템 어두움 글자 hex
- B05 · B06 · B08 그림 CSS stroke · fill 125 줄 · --b06-tag = --draw-*
- 캔버스 · 3D 읽기 = B05 노선 편집 지도 · 노선 추천 지도 · 3D 바탕 · B04 3D 바탕 · 지도 MC 글 · D03 고른 측점 · CAD 바탕 · 격자
- 지킴 시험 test_551_2_draw_tokens(화면 변수를 모두 바꿔도 그림 풀린 색 같음)
- 기존 시험 7 개 = 그림 줄이 옛 --color-* 이름을 잡던 단정을 --draw-* 로 고침(값 같음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:59:03 +09:00
eomsangdonandClaude Opus 5.5 aaac17db4c feat(ui-kit): 부품 모음 화면 틀 — 시스템 관리자만 · 밝음 / 어두움 나란히 · 상태 다섯 칸 · 크기 여덟 단 (PLAN 552-1)
- 대시보드 세부 페이지 `#/b01-account?view=ui-kit` 등록부 한 줄(시스템 관리 묶음 · SYSTEM_ADMIN 만)
- ui_template_ui_kit.ts — 테마 칸마다 iframe(data-theme)에 지금 CSS 를 옮겨 실제 값으로 그림
- ui_template_ui_kit_parts.ts — 표준 부품 30 줄(552-2 기본 · 552-3 묶음 · 552-4 틀) · 머리 = 부품 규칙(자리 · 이름 · data-size · data-state · 색 · 아이콘)
- 지금 PageKit · 공용 부품 견본(단추 · 입력 · 고르기 · 탭 · 배지 · 표 · ⋯ · 빈 상태 · 설정 줄 · 아이콘) · 나머지 빈 자리
- test_546 시스템 메뉴 기대값에 ui-kit 더함 · 새 지킴 시험 test_552_1_ui_kit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:49:09 +09:00
eomsangdonandClaude Opus 5.5 85ae6319e4 chore(요구): 요구 378 — 단추 크기 여덟 단(18~42) · 글꼴 = 지금 Noto Sans KR 파일 실음 · Pretendard 안 씀 · 진행
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:39:11 +09:00
eomsangdonandClaude Opus 5.5 8721bd7b18 chore(요구): 요구 377 — 단추 크기 꼴 다섯 이상 · 프로젝트 안 = ☰ 서랍 · 글꼴 물음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:28:23 +09:00
eomsangdonandClaude Opus 5.5 41973e9874 chore(요구): 요구서 375 고침(요구 376) — 범위 전체 영역 · 템플릿 부품 상자 먼저 · 부품 모음 화면
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:23:27 +09:00
eomsangdonandClaude Opus 5.5 c914702a84 chore(요구): 요구 376 — 테마 대상 = 전체 영역 · 템플릿 요소를 먼저 만들고 재활용(물음)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:22:38 +09:00
eomsangdonandClaude Opus 5.5 efb9b9ca67 chore(요구): 요구서 375 전역 테마 — 조사 둘(토큰 · 공용 부품 · 페이지별 위험) · 공통 값 한 벌 · 그림 색 고정 · 단계 셋
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 19:53:00 +09:00
eomsangdonandClaude Opus 5.5 66f5858e54 chore(요구): 요구 375 — 대시보드 테마 · 색 · 글꼴 · 요소를 프로젝트 안 화면까지 전역 반영(조사 · 요구서 · 계획서)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 19:34:51 +09:00
eomsangdonandClaude Opus 5.5 43d0ccb63f merge(B01): main_laptop_1(549-2 시스템 관리 · 내 정보) 합침 — 옛 구역 정리 · 시험 옮김
- B01_Dashboard_UI_Legacy.ts: 세부 페이지가 다 갈아 끼워 옛 구역 · mountLegacy 지움 · 고른 회사 자리 · 상태 타입만 남김
- B01_Dashboard_UI_Views.ts: 쓰는 곳 없는 legacy 도우미 지움
- B01_Dashboard_UI_Style_Pages.css: 배지 색(549-1) · 시스템 관리 꼴(549-2) 둘 다 살림
- test_b01_settings_cards · test_309_2: 지운 옛 카드 부품 대신 settingLinks · 설계 기본값 페이지 · 사람 페이지를 읽음(낡은 시험)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:33:32 +09:00
eomsangdon bbedceeae1 Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1
# Conflicts:
#	B01_Dashboard/B01_Dashboard_UI_Views.ts
#	resources/tester/test_546_b01_shell.py
2026-10-26 18:28:47 +09:00
eomsangdonandClaude Sonnet 5.5 adc64ddcf3 fix(B01): 시스템 관리 페이지 다듬기 — 로그 동작 · 대상 한글 추가 · 회사 상세 되돌아가기 링크 · 단추 눌림 색을 셸 강조 색으로 (PLAN 549-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 18:27:35 +09:00
eomsangdon a924f466bf Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_4
# Conflicts:
#	B01_Dashboard/B01_Dashboard_UI_Views.ts
#	resources/tester/test_546_b01_shell.py
2026-10-26 18:27:08 +09:00
eomsangdonandClaude Sonnet 5.5 7e34152de8 feat(B01): 받은 예정노선 · 임시 보관함 · 사람 · 회사 정보 · 회사 설계 기본값 세부 페이지 (549-1)
- 새 페이지 다섯(View_Assigned · Temp · People · Company · CompanyDefaults) · 등록부 다섯 줄 교체
- 표 줄 40 · 세로선 없음 · 줄 끝 ⋯ · 한글 배지 · 빈 상태 · 사람 탭 가입 요청 수 배지 · 보관함 남은 날 막대(7 일 안 빨강)
- 옛 구역 정리(Legacy · Company · AssignedPlans · TempUpload 다시 씀) · 시험 test_549 · test_546 stub 일반화

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 18:26:27 +09:00
eomsangdon bed7957a65 Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1
# Conflicts:
#	B01_Dashboard/B01_Dashboard_UI_Views.ts
#	resources/tester/test_546_b01_shell.py
2026-10-26 18:25:26 +09:00
eomsangdonandClaude Opus 5.5 c5518a8783 fix(B01): 홈 머리 글 — 꼴 고정 때 「진행 중 N 개 · 작게/크게 고정」 (PLAN 547-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:22:58 +09:00
eomsangdonandClaude Opus 5.5 729f4c75b8 feat(B01): 대시보드 홈 — 진행 중 수에 따라 꼴이 저절로(1 큰 카드 · 2~3 중간 · 4~8 한 줄 · 9 넘음 요약) (PLAN 547-2)
- 등록부 home 줄 = mountHome · 썸네일 선 · 단계 점 · 「다음: 지금 단계」 · [이어서 하기] = 그 단계 화면 · [자세히] = 상세
- 4~8 = 팀원별 · 단계 분포 · 14 일 넘게 그대로 표시 · 9 넘음 = 요약 숫자 · 회사별 막대 · 종류 원(인라인 SVG)
- 오른쪽 = 처리할 일 · 임시 보관함 · 회사(회사 관리자) · 예정노선(산림청) · 처음 = 세 걸음 · 꼴 고정(자동 · 작게 · 크게) 기억
- 홈 역할 차례 = 셸 roleOf 와 같게(회사 관리자가 산림청 앞)
- test_546 등록부 시험 — import 한 mount 이름을 빈 함수로 저절로(페이지 줄 갈아 끼워도 그대로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:20:21 +09:00
eomsangdonandClaude Sonnet 5.5 70786cef9e feat(B01): 시스템 관리 · 내 정보 세부 페이지 여섯 — 개요 · 회사 · 사용자 · 설계 기본값 · 로그 · 내 정보 (PLAN 549-2)
- 개요 = 숫자 카드 넷 + CPU · 메모리 · 디스크 게이지(70 % 주황 · 90 % 빨강) + 7 일 SVG 그래프
- 회사 = 목록(찾기 · ⋯) → 상세(정보 · 사람 · 프로젝트 탭) · 사용자 = 회사 · 역할 · 찾기 거르기 · ⋯
- 설계 기본값 = 설정 카드를 묶음 제목 + 줄로 · 시스템 로그 = 동작 · 기간 · 찾기 거르기 + 이전 기록 더 보기
- 내 정보 = Linear 꼴 설정 줄(기본정보 · 비밀번호 · 테마 · 내 설계 기본값)
- 옛 구역(리소스 패널 · 옛 내 정보 폼) 지움 · 지킴 시험 test_549 추가 · test_546 stub 맞춤

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 18:19:47 +09:00
eomsangdon 9826538c74 Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1 2026-10-26 18:19:35 +09:00
eomsangdonandClaude Opus 5.5 13b417120c test(B01): 셸 등록부 시험 — 가져온 mount 함수를 저절로 흉내 (PLAN 548)
등록부에 세부 페이지 mount 가 늘면 고정 흉내 셋(mountLegacy · mountGuide · mountChat) 밖 이름이
ReferenceError — import 줄에서 이름을 읽어 빈 함수로 둠.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:18:48 +09:00
eomsangdonandClaude Opus 5.5 95a2bed75b feat(B01): 등록부 projects · project 줄 = 새 목록 · 상세 (PLAN 548)
- 셸 등록부 projects · project 줄을 mountProjectList · mountProjectDetail 로 갈아 끼움
- ⋯ 메뉴 오른쪽 끝 = 단추 오른쪽(표 · 상세 같은 함수)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:15:34 +09:00
eomsangdon 5aaf7f4e65 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_3 2026-10-26 18:09:03 +09:00
eomsangdon 3274d4b107 Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_2 2026-10-26 18:07:55 +09:00
eomsangdonandClaude Opus 5.5 bc1c8b44e8 feat(B01): 홈 API — 역할별 진행 중 카드 · 처리할 일 · 회사 · 산림청 · 시스템 요약 (PLAN 547-2)
- GET /api/dashboard/home — role(USER · ADMIN · FOREST · SYSTEM_ADMIN) · 진행 중 카드(최근 수정 순 · 썸네일 앞 8 개)
- 처리할 일 = 새로 받은 · 업데이트 있는 예정노선 · 가입 요청 수 · 7 일 안 만료 보관 파일 · 보관 묶음 수
- 회사 관리자 = 회사 이름 · 팀원 수 · 산림청 = 예정노선 연도 상태별 수 · 업체 지정 대기 · 시스템 = 회사 · 사용자 수 · 서버 지금 값

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:07:54 +09:00
13837 changed files with 2615805 additions and 122803 deletions
+5
View File
@@ -88,3 +88,8 @@ PROJECT_DELETE_HARD_ENABLED=True
AUTH_OTP_DISABLED=True
# 대시보드 지식 챗봇(B01_Dashboard_Knowledge_Chat) NVIDIA 키
NVIDIA_API_KEY=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
# 노선분석 산사태위험지도(행안부 생활안전지도 WMS IF_0046 · D03_PlanAnalysis_Landslide) 키 — 비우면 겹 꺼짐
SAFEMAP_API_KEY=MFO8CY4O-MFO8-MFO8-MFO8-MFO8CY4O6X
DATA_GO_KR_API_KEY=0j5WZHeNxSkdPVSLXUsx9kVikXEN%2BDc%2Byv68jz8IpKFffjbZkRiQrJO78y6rxPCTqhiSOW4lDxpid0twkSVQ9g%3D%3D
# 지오빅데이터(KIGAM) 오픈API 키 · 요구 597
GEOBIGDATA_API_KEY=FVLh4UQc9x4bvYhXJWRITxF4v3y2ns
+7
View File
@@ -42,6 +42,8 @@ storage/
#resources/
# 전국 등고선 gpkg 22GB — 영구 추적 제외 (배포서버 구성 시 직접 배치)
resources/data_global_contours/national_contours.gpkg
# 산림 주제도 원본(산림청 공개 파일) — 추적 제외
resources/data_forest/
# 로그 파일
*.log
@@ -76,3 +78,8 @@ config/design_calc_node/
config/e03_walls_node/
~$*
# graphify 결과(폐기 도구) — 추적 안 함
graphify-out/
# 큰 자료 시놀로지 전달용 임시 폴더(git 밖)
/_sync_data_forest/
+27 -57
View File
@@ -1,10 +1,11 @@
/* ☰ [설정] 창(PLAN 332-2) — 탭 줄 · 휠 방향 고름 · 표 */
/* ☰ [설정] 창(PLAN 332-2) — 탭 줄 = 부품 보조 탭 · 휠 방향 = 부품 라디오 · CAD 별칭 표 = 부품 표 · 규칙 줄 = 부품 값 목록 규칙 꼴 */
.app-settings .ui-text[hidden] {
display: none; /* 부품 보조 글은 display:flex 라 hidden 을 이김 */
}
.app-settings__tabs {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-8);
padding-bottom: var(--spacing-8);
border-bottom: 1px solid var(--color-border);
}
.app-settings__radios {
@@ -13,25 +14,13 @@
gap: var(--spacing-16);
}
.app-settings__radios > label {
display: inline-flex;
align-items: center;
gap: var(--spacing-8);
.app-settings__radios > .ui-checkbox {
min-height: 32px;
cursor: pointer;
}
.app-settings__table {
width: 100%;
border-collapse: collapse;
}
.app-settings__table th,
.app-settings__table td {
padding: var(--spacing-4) var(--spacing-8);
border-bottom: 1px solid var(--color-border);
text-align: left;
vertical-align: middle;
/* 표 머리는 창 몸통을 따라 붙음 — 부품 표 겉틀의 가로 스크롤은 풂 */
.app-settings .ui-table-wrap {
overflow: visible;
}
.app-settings__table thead th {
@@ -40,51 +29,32 @@
background: var(--color-surface-raised);
}
.app-settings__table input[aria-invalid="true"] {
border-color: var(--color-danger);
/* 588 — 찾기 줄 · 묶음 사이 · 키 배지 줄 */
.app-settings__findbar {
display: flex;
gap: var(--spacing-8);
align-items: flex-start;
position: sticky;
top: 0;
z-index: 1;
padding-bottom: var(--spacing-8);
background: var(--color-surface-raised);
}
.app-settings__error {
color: var(--color-danger);
.app-settings__findbar > .ui-field {
flex: 1;
}
.app-settings__error:empty {
.app-settings__group + .app-settings__group {
margin-top: var(--spacing-4);
}
.app-settings__group[hidden] {
display: none;
}
.app-settings__rules {
margin: 0 0 var(--spacing-8);
padding: 0;
list-style: none;
}
.app-settings__rules > li {
display: grid;
grid-template-columns: minmax(0, 15em) 1fr;
align-items: center;
gap: var(--spacing-12);
padding: var(--spacing-4) 0;
border-bottom: 1px solid var(--color-border);
white-space: nowrap;
}
.app-settings__keys {
display: flex;
display: inline-flex;
flex-wrap: wrap;
gap: var(--spacing-4);
}
.app-settings__keys:only-child {
grid-column: 1 / -1;
}
.app-settings__keys > kbd {
padding: 0 var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface-raised);
color: var(--color-text);
font-family: var(--font-body);
font-size: 0.85em;
line-height: 1.7;
}
+90 -65
View File
@@ -1,19 +1,26 @@
/* =============================================================================
* app_settings.ts
* ☰ [설정] 창(PLAN 332-2 · 요구 224-8) — 탭 셋: ① 마우스 ② CAD 단축키 ③ 다른 곳 단축키(읽기 전용).
* ☰ [설정] 창(PLAN 332-2 · 588 · 요구 224-8 · 445) — 탭 셋: ① 마우스(휠 방향 · 화면 옮기기 단추) ② CAD 단축키 ③ 다른 곳 단축키.
*
* - 고침은 창 안 사본에만 · [저장] 때만 계정 pref(`input-prefs`)로 한 번 씀 — 읽기는 `input_prefs.ts`
* - 저장 안 한 고침이 있으면 닫을 때 물음(askLeave) · 잘못 있는 별칭은 저장 안 함
* - 저장 안 한 고침이 있으면 닫을 때 물음(askLeave) · 잘못 있는 별칭 · 겹친 키는 저장 안 함
* - ② · ③ 표 = 단축키 등록부(`key_registry.ts`) — 바꾼 키만 `keys` 에
* - 로그인 전엔 이 브라우저에만 남음(계정 올리기는 `b_page_state` 가 로그인 뒤에)
* ========================================================================== */
import { createBadge } from "@ui/ui_template_badge";
import { createCheckbox } from "@ui/ui_template_checkbox";
import { createKv } from "@ui/ui_template_kv";
import { createButton, el, showToast } from "@ui/ui_template_elements";
import { askLeave } from "@ui/ui_template_leave_dialog";
import { t as L } from "@ui/ui_template_locale";
import { openModal } from "@ui/ui_template_modal";
import { writeState } from "./b_page_state";
import { inputPrefs, type InputPrefs, type WheelZoom } from "./input_prefs";
import { inputPrefs, type InputPrefs, type PanButton, type WheelZoom } from "./input_prefs";
import { fillTabs } from "./b_page_tabs";
import { buildCadAliasTab, checkCadAliases } from "./app_settings_cad";
import { findPanel, keyRows } from "./app_settings_keys";
import { KEY_ACTIONS, checkKeys, type KeyScope } from "./key_registry";
import "./app_settings.css";
type LocaleKey = Parameters<typeof L>[0];
@@ -30,6 +37,12 @@ const WHEEL: [WheelZoom, LocaleKey][] = [
["push", "App_Settings_Wheel_Push"],
];
const PAN: [PanButton, LocaleKey][] = [
["left", "App_Settings_Pan_Left"],
["middle", "App_Settings_Pan_Middle"],
["right", "App_Settings_Pan_Right"],
];
const MOUSE_RULES: LocaleKey[] = [
"App_Settings_Rule_Pan",
"App_Settings_Rule_Pick",
@@ -37,106 +50,111 @@ const MOUSE_RULES: LocaleKey[] = [
"App_Settings_Rule_Pinch",
];
/** ③ 안내 — [자리, 키] */
const KEY_ROWS: [LocaleKey, LocaleKey][] = [
["App_Settings_Keys_All", "App_Settings_Keys_All_Keys"],
["App_Settings_Keys_Profile", "App_Settings_Keys_Profile_Keys"],
["App_Settings_Keys_RouteEdit", "App_Settings_Keys_RouteEdit_Keys"],
["App_Settings_Keys_Section", "App_Settings_Keys_Section_Keys"],
["App_Settings_Keys_Cad", "App_Settings_Keys_Cad_Keys"],
["App_Settings_Keys_Table", "App_Settings_Keys_Table_Keys"],
["App_Settings_Keys_M02", "App_Settings_Keys_M02_Keys"],
];
/** ③ 자리 = CAD 뺀 등록부 자리(CAD 는 ② 탭 「키 · 마우스」) */
const OTHER_SCOPES = [...new Set(KEY_ACTIONS.map((a) => a.scope))].filter(
(scope): scope is KeyScope => scope !== "cad",
);
/** 비교 · 저장 꼴 — 별칭 명령 차례를 맞춤 */
const normalize = (p: InputPrefs): InputPrefs => ({
wheelZoom: p.wheelZoom,
panButton: p.panButton,
keys: Object.fromEntries(Object.entries(p.keys).sort(([a], [b]) => (a < b ? -1 : 1))),
cadAliases: Object.fromEntries(Object.entries(p.cadAliases).sort(([a], [b]) => (a < b ? -1 : 1))),
});
const snapshot = (p: InputPrefs): string => JSON.stringify(normalize(p));
/** 규칙 목록 — 한 줄 = 「입력 = 동작」 · 입력은 키 칩(` · ` 로 여럿) · 동작 없는 줄은 칩만 */
/** 규칙 목록 = 값 목록 부품 규칙 꼴 — 한 줄 = 「입력 = 동작」 · 입력은 키 칩(` · ` 로 여럿) · 동작 없는 줄은 칩만 */
function ruleList(keys: LocaleKey[]): HTMLElement {
const items = keys
const rows = keys
.flatMap((key) => L(key).split("\n"))
.map((line) => {
const at = line.indexOf(" = ");
const input = at < 0 ? line : line.slice(0, at);
return el("li", {
children: [
el("span", {
className: "app-settings__keys",
children: input.split(" · ").map((text) => el("kbd", { text })),
}),
...(at < 0 ? [] : [el("span", { text: line.slice(at + 3) })]),
],
});
const chips = document.createDocumentFragment();
chips.append(...input.split(" · ").map((text) => createBadge(text, "neutral")));
return { label: chips, value: at < 0 ? undefined : line.slice(at + 3) };
});
return el("ul", { className: "app-settings__rules", children: items });
return createKv(rows, { rule: true });
}
function radios<T extends string>(
name: string,
label: LocaleKey,
options: [T, LocaleKey][],
value: T,
onChange: (value: T) => void,
): HTMLElement {
const group = el("div", {
className: "app-settings__radios",
attrs: { role: "radiogroup", "aria-label": L(label) },
});
for (const [mode, key] of options) {
group.append(
createCheckbox(L(key), {
radio: name,
value: mode,
checked: value === mode,
onChange: () => onChange(mode),
}).root,
);
}
return group;
}
function mouseTab(draft: InputPrefs): HTMLElement {
const group = el("div", {
className: "app-settings__radios",
attrs: { role: "radiogroup", "aria-label": L("App_Settings_Wheel") },
});
for (const [mode, key] of WHEEL) {
const input = el("input", {
attrs: { type: "radio", name: "app-settings-wheel", value: mode },
});
input.checked = draft.wheelZoom === mode;
input.addEventListener("change", () => (draft.wheelZoom = mode));
group.append(el("label", { children: [input, L(key)] }));
}
return el("div", {
children: [
el("h4", { text: L("App_Settings_Wheel") }),
group,
radios("app-settings-wheel", "App_Settings_Wheel", WHEEL, draft.wheelZoom, (mode) => {
draft.wheelZoom = mode;
}),
el("h4", { text: L("App_Settings_Pan") }),
radios("app-settings-pan", "App_Settings_Pan", PAN, draft.panButton, (mode) => {
draft.panButton = mode;
}),
el("h4", { text: L("App_Settings_Rules") }),
ruleList(MOUSE_RULES),
],
});
}
function keysTab(): HTMLElement {
return el("div", {
children: KEY_ROWS.flatMap(([place, keys]) => [el("h4", { text: L(place) }), ruleList([keys])]),
});
}
/** ☰ [설정] — 창을 열고 지금 설정을 사본으로 고침 */
export function openAppSettings(): void {
const draft = normalize(inputPrefs());
let saved = snapshot(draft);
const cad = buildCadAliasTab(draft.cadAliases);
/** 키를 고칠 때마다 — 두 탭 칸 글 · 겹침 표시 다시 */
const keysChanged = (): void => {
const errors = checkKeys(draft.keys);
for (const rows of [cad.keys, other]) {
rows.refresh();
rows.mark(errors);
}
};
const cad = buildCadAliasTab(draft.cadAliases, draft.keys, keysChanged);
const other = keyRows(OTHER_SCOPES, draft.keys, keysChanged);
const keysPanel = findPanel(other.groups, [el("p", { text: L("App_Settings_Key_Help") })], () => {
for (const id of Object.keys(draft.keys)) if (!id.startsWith("cad.")) delete draft.keys[id];
keysChanged();
});
const panels: Record<TabId, HTMLElement> = {
mouse: mouseTab(draft),
cad: cad.root,
keys: keysTab(),
keys: keysPanel.root,
};
const tabs = el("div", {
className: "app-settings__tabs ui-subtabs",
attrs: { role: "tablist" },
});
const tabs = el("div", { className: "app-settings__tabs" });
const show = (id: TabId): void => {
tabs.replaceChildren(
...TABS.map(([tab, key]) => {
const button = createButton({
label: L(key),
variant: tab === id ? "filled" : "ghost",
size: "sm",
onClick: () => show(tab),
});
button.setAttribute("role", "tab");
button.setAttribute("aria-selected", String(tab === id));
button.dataset.settingsTab = tab;
return button;
}),
);
fillTabs(tabs, {
tabs: TABS.map(([tab, key]) => ({ key: tab, label: L(key) })),
value: id,
kind: "pill",
data: "settingsTab",
onChange: (tab) => show(tab as TabId),
});
for (const [tab, panel] of Object.entries(panels)) panel.hidden = tab !== id;
};
/** [저장] — 잘못 있는 별칭이면 CAD 탭을 보이고 안 씀 */
/** [저장] — 잘못 있는 별칭 · 겹친 키면 그 탭을 보이고 안 씀 */
const save = (): boolean => {
const errors = checkCadAliases(draft.cadAliases);
cad.mark(errors);
@@ -145,6 +163,13 @@ export function openAppSettings(): void {
showToast(L("App_Settings_Cad_Invalid"), "error");
return false;
}
const keyErrors = Object.keys(checkKeys(draft.keys));
if (keyErrors.length) {
keysChanged();
show(keyErrors.some((id) => id.startsWith("cad.")) ? "cad" : "keys");
showToast(L("App_Settings_Key_Invalid"), "error");
return false;
}
if (snapshot(draft) !== saved) writeState("input-prefs", normalize(draft));
saved = snapshot(draft);
showToast(L("App_Settings_Saved"), "success");
+60 -33
View File
@@ -1,6 +1,6 @@
/* =============================================================================
* app_settings_cad.ts
* ☰ [설정] ② CAD 단축키 탭(PLAN 332-2) — 명령 표(명령 · 한글 이름 · 기본 별칭 · 내 별칭) · 검사.
* ☰ [설정] ② CAD 단축키 탭(PLAN 332-2 · 588-1) — 찾기 · 키 · 마우스 묶음 · 리본 이름 묶음 명령 표(명령 · 한글 이름 · 기본 별칭 · 내 별칭) · 검사.
*
* - 명령 목록 = `openwebcad/src/commands/command-catalog.json`(registry 거울 · commands.*.ts 는 xstate 가 딸려 와서 JSON 한 벌)
* - acad.pgp 식 덮어쓰기 — CAD 는 명령 이름 > 내 별칭 > 기본 별칭 순서로 찾음(배선은 332-3)
@@ -9,7 +9,11 @@
import catalog from "../B07_DesignDetail/openwebcad/src/commands/command-catalog.json";
import { el } from "@ui/ui_template_elements";
import { createInputField } from "@ui/ui_template_input";
import { createText } from "@ui/ui_template_text";
import { t as L } from "@ui/ui_template_locale";
import { cadCommandGroups } from "./app_settings_find";
import { findGroup, findPanel, keyRows, type FindRow, type KeyRows } from "./app_settings_keys";
export interface CadCommandRow {
id: string;
@@ -62,18 +66,25 @@ export function checkCadAliases(
return errors;
}
/** 탭 몸 — `aliases` 를 칸 입력대로 고쳐 씀 · `mark` 로 잘못 칸 표시 */
export function buildCadAliasTab(aliases: Record<string, string[]>): {
/** 탭 몸(588-1) — 찾기 · 「키 · 마우스」 묶음(등록부 CAD 줄) · 리본 이름 묶음 · `aliases` · `keys` 를 칸 입력대로 고쳐 씀 */
export function buildCadAliasTab(
aliases: Record<string, string[]>,
keys: Record<string, string[]>,
onKeysChange: () => void,
): {
root: HTMLElement;
mark: (errors: Record<string, AliasError>) => void;
keys: KeyRows;
count: () => number;
} {
const message = el("p", { className: "app-settings__error", attrs: { role: "alert" } });
const message = createText("", "error");
message.hidden = true;
const inputs = new Map<string, HTMLInputElement>();
const rows = CAD_COMMANDS.map((command) => {
const input = el("input", {
className: "ui-input ui-input--sm",
attrs: { type: "text", "aria-label": `${command.id} ${L("App_Settings_Cad_Mine")}` },
});
const byId = new Map(CAD_COMMANDS.map((command) => [command.id, command]));
const row = (command: CadCommandRow): FindRow => {
const field = createInputField({ size: 24 });
const input = field.input;
input.setAttribute("aria-label", `${command.id} ${L("App_Settings_Cad_Mine")}`);
input.value = (aliases[command.id] ?? []).join(", ");
input.dataset.cadCommand = command.id;
input.addEventListener("input", () => {
@@ -82,25 +93,30 @@ export function buildCadAliasTab(aliases: Record<string, string[]>): {
else delete aliases[command.id];
});
inputs.set(command.id, input);
const cells = [command.id, command.label, command.aliases.join(", ")].map((text) =>
el("td", { text }),
);
return el("tr", { children: [...cells, el("td", { children: [input] })] });
});
const head = el("tr", {
children: (
[
"App_Settings_Cad_Command",
"App_Settings_Cad_Label",
"App_Settings_Cad_Default",
"App_Settings_Cad_Mine",
] as const
).map((key) => el("th", { text: L(key) })),
});
const table = el("table", {
className: "app-settings__table",
children: [el("thead", { children: [head] }), el("tbody", { children: rows })],
});
return {
cells: [command.id, command.label, command.aliases.join(", "), field.root],
texts: () => [command.id, command.label, ...command.aliases, input.value],
};
};
const columns = (
[
"App_Settings_Cad_Command",
"App_Settings_Cad_Label",
"App_Settings_Cad_Default",
"App_Settings_Cad_Mine",
] as const
).map((key) => ({ label: L(key) }));
const keyRowsCad = keyRows(["cad"], keys, onKeysChange, () => L("App_Settings_Cad_Keys"));
const groups = [
...keyRowsCad.groups,
...cadCommandGroups(CAD_COMMANDS.map((c) => c.id)).map((group) =>
findGroup(
group.label || L("App_Settings_Cad_Other"),
columns,
group.ids.map((id) => row(byId.get(id)!)),
),
),
];
const mark = (errors: Record<string, AliasError>): void => {
for (const [id, input] of inputs) {
const error = errors[id];
@@ -109,10 +125,21 @@ export function buildCadAliasTab(aliases: Record<string, string[]>): {
input.title = error ? L(ERROR_TEXT[error]) : "";
}
const kinds = [...new Set(Object.values(errors))];
message.textContent = kinds.map((k) => L(ERROR_TEXT[k])).join(" · ");
message.lastChild!.textContent = kinds.map((k) => L(ERROR_TEXT[k])).join(" · ");
message.hidden = !kinds.length;
};
const root = el("div", {
children: [el("p", { text: L("App_Settings_Cad_Help") }), message, table],
});
return { root, mark };
/** [모두 기본값] — 내 별칭 · CAD 바꾼 키 비움 */
const resetAll = (): void => {
for (const id of Object.keys(aliases)) delete aliases[id];
for (const input of inputs.values()) input.value = "";
for (const id of Object.keys(keys)) if (id.startsWith("cad.")) delete keys[id];
mark({});
onKeysChange();
};
const panel = findPanel(
groups,
[el("p", { text: L("App_Settings_Cad_Help") }), message],
resetAll,
);
return { root: panel.root, mark, keys: keyRowsCad, count: panel.count };
}
+44
View File
@@ -0,0 +1,44 @@
/* =============================================================================
* app_settings_find.ts
* ☰ [설정] 단축키 탭 셈(PLAN 588-1) — CAD 명령 묶음 · 찾기 판정. 화면 · 저장소 안 씀.
*
* - 묶음 = 리본 탭 › 패널 이름 그대로(`openwebcad/src/ribbon/ribbon.config.ts` · 순수 자료) · 두 패널에 있으면 앞 패널 · 리본에 없는 명령 = 기타
* - 찾기 = 띄어 나눈 낱말이 모두 어느 글에든 들면 맞음 · 대소문자 무시
* 시험 = `resources/tester/test_588_key_registry.py`
* ========================================================================== */
import { RIBBON_TABS } from "../B07_DesignDetail/openwebcad/src/ribbon/ribbon.config";
export interface CadGroup {
/** 「홈 › 그리기」 · 기타는 빈 글 */
label: string;
ids: string[];
}
/** 명령 id 들 → 리본 묶음(리본 차례) + 끝에 기타(`label` 빈 글) */
export function cadCommandGroups(ids: string[]): CadGroup[] {
const known = new Set(ids);
const seen = new Set<string>();
const groups: CadGroup[] = [];
for (const tab of RIBBON_TABS) {
for (const panel of tab.panels) {
const mine = [...(panel.big ?? []), ...(panel.commands ?? []), ...(panel.overflow ?? [])]
.filter((id) => known.has(id) && !seen.has(id))
.filter((id) => seen.add(id));
if (mine.length) groups.push({ label: `${tab.label} › ${panel.label}`, ids: mine });
}
}
const rest = ids.filter((id) => !seen.has(id));
if (rest.length) groups.push({ label: "", ids: rest });
return groups;
}
/** 찾기 — 빈 찾기 = 맞음 · 낱말마다 어느 글에든 들어야 */
export function matchesFind(query: string, texts: string[]): boolean {
const hay = texts.join("\n").toLowerCase();
return query
.toLowerCase()
.split(/\s+/)
.filter(Boolean)
.every((word) => hay.includes(word));
}
+191
View File
@@ -0,0 +1,191 @@
/* =============================================================================
* app_settings_keys.ts
* ☰ [설정] 단축키 표(PLAN 588) — 접이 묶음 · 찾기 · 키 기록 칸 · 줄마다 [기본값]. CAD 탭 · 다른 곳 탭이 같이 씀.
*
* - 표 줄 = 등록부(`key_registry.ts`) — 손글 안내 없음 · 못 바꾸는 줄은 키 배지만
* - 키 기록 = 칸을 누르고 새 키를 누름(Tab = 초점 넘김 · Esc = 그만) · 기본과 같으면 바꾼 키에서 뺌
* - 고침은 창 안 사본(`keys`)에만 · [저장] 은 `app_settings.ts`
* ========================================================================== */
import { createBadge } from "@ui/ui_template_badge";
import { createButton } from "@ui/ui_template_button";
import { attachCollapsible, createCollapsibleHead } from "@ui/ui_template_collapsible";
import { el } from "@ui/ui_template_elements";
import { createEmpty } from "@ui/ui_template_empty";
import { createInputField } from "@ui/ui_template_input";
import { currentLanguageIndex, t as L } from "@ui/ui_template_locale";
import { tableRow, tableShell, type Cell, type Column } from "@ui/ui_template_table";
import { matchesFind } from "./app_settings_find";
import {
KEY_ACTIONS,
KEY_SCOPES,
actionKeys,
chordOf,
keyLabel,
type KeyError,
type KeyScope,
} from "./key_registry";
type LocaleKey = Parameters<typeof L>[0];
const KEY_ERROR_TEXT: Record<KeyError, LocaleKey> = {
dup: "App_Settings_Key_ErrDup",
reserved: "App_Settings_Key_ErrReserved",
letter: "App_Settings_Key_ErrLetter",
};
export interface FindRow {
cells: Cell[];
/** 찾기에 쓰는 글 — 찾을 때마다 읽음(칸에 친 글도) */
texts: () => string[];
}
export interface FindGroup {
root: HTMLElement;
/** 맞는 줄 수 */
filter: (query: string) => number;
}
/** 접이 묶음 하나 — 머리(이름 · 줄 수 배지) + 표 · 처음엔 접힘 · 찾는 동안 맞는 줄만 펼침 */
export function findGroup(title: string, columns: Column[], rows: FindRow[]): FindGroup {
const badge = createBadge(String(rows.length), "neutral");
const head = createCollapsibleHead({ title, actions: [badge], level: 4 });
const { root: table, tbody } = tableShell(columns, { density: "data" });
table.classList.add("app-settings__table");
const trs = rows.map((row) => tableRow(columns, row.cells));
tbody.append(...trs);
const root = el("section", {
className: "ui-collapsible is-collapsed app-settings__group",
children: [head.root, table],
});
const filter = (query: string): number => {
let shown = 0;
rows.forEach((row, i) => {
const hit = matchesFind(query, row.texts());
trs[i].hidden = !hit;
if (hit) shown += 1;
});
badge.textContent = String(shown);
root.hidden = !shown;
root.classList.toggle("is-collapsed", !query.trim());
return shown;
};
return { root, filter };
}
/** 찾기 칸 + [모두 기본값] + 묶음들 + 0건 안내 — `count` = 맞는 줄 수(시험 · 검증) */
export function findPanel(
groups: FindGroup[],
head: HTMLElement[],
onResetAll: () => void,
): { root: HTMLElement; count: () => number } {
const empty = createEmpty({ text: L("App_Settings_Find_Empty"), small: true });
empty.hidden = true;
let count = 0;
const find = createInputField({
type: "search",
size: 28,
placeholder: L("App_Settings_Find"),
onInput: (query) => {
count = groups.reduce((sum, group) => sum + group.filter(query), 0);
empty.hidden = count > 0;
},
});
find.input.setAttribute("aria-label", L("App_Settings_Find"));
find.input.dataset.settingsFind = "";
const reset = createButton({
label: L("App_Settings_Reset_All"),
variant: "ghost",
size: 28,
onClick: onResetAll,
});
const bar = el("div", { className: "app-settings__findbar", children: [find.root, reset] });
const root = el("div", { children: [bar, ...head, ...groups.map((g) => g.root), empty] });
attachCollapsible(root);
count = groups.reduce((sum, group) => sum + group.filter(""), 0);
return { root, count: () => count };
}
const text = (pair: readonly [string, string]): string => pair[currentLanguageIndex] ?? pair[0];
const badges = (keys: readonly string[]): HTMLElement =>
el("span", {
className: "app-settings__keys",
children: keys.map((key) => createBadge(keyLabel(key), "neutral")),
});
export interface KeyRows {
groups: FindGroup[];
/** 잘못 표시 */
mark: (errors: Record<string, KeyError>) => void;
/** 사본이 바뀐 뒤 칸 글 · [기본값] 다시 맞춤 */
refresh: () => void;
}
/** 등록부 자리들의 묶음 — `keys` 사본을 고쳐 씀 · `onChange` = 고칠 때마다(겹침 다시 셈) */
export function keyRows(
scopes: KeyScope[],
keys: Record<string, string[]>,
onChange: () => void,
titleOf: (scope: KeyScope) => string = (scope) => text(KEY_SCOPES[scope].label),
): KeyRows {
const columns: Column[] = [
{ label: L("App_Settings_Key_Action") },
{ label: L("App_Settings_Key_Key"), width: "13rem" },
{ label: "", width: "6rem" },
];
const fields = new Map<string, { set: () => void; setError: (msg?: string) => void }>();
const groups = scopes.map((scope) => {
const rows = KEY_ACTIONS.filter((a) => a.scope === scope).map((action): FindRow => {
const label = text(action.label);
const texts = [label, ...action.keys, ...action.keys.map(keyLabel)];
if (action.fixed) return { cells: [label, badges(action.keys), ""], texts: () => texts };
const field = createInputField({ size: 24 });
const input = field.input;
input.readOnly = true;
input.dataset.keyAction = action.id;
input.setAttribute("aria-label", `${label} ${L("App_Settings_Key_Key")}`);
const defaults = action.keys.map(keyLabel).join(" · ");
const reset = createButton({
label: L("App_Settings_Reset"),
variant: "ghost",
size: 24,
onClick: () => {
delete keys[action.id];
onChange();
},
});
reset.title = `${L("App_Settings_Reset")}: ${defaults}`;
reset.dataset.keyReset = action.id;
input.addEventListener("keydown", (event) => {
if (event.key === "Tab") return;
event.preventDefault();
event.stopPropagation(); // 창 닫기 · 페이지 키로 안 샘
if (event.key === "Escape") return input.blur();
const chord = chordOf(event);
if (!chord) return;
if (action.keys.length === 1 && action.keys[0] === chord) delete keys[action.id];
else keys[action.id] = [chord];
onChange();
});
fields.set(action.id, {
set: () => {
input.value = actionKeys(action.id, keys).map(keyLabel).join(" · ");
reset.disabled = !keys[action.id];
},
setError: field.setError,
});
return { cells: [label, field.root, reset], texts: () => [...texts, input.value] };
});
return findGroup(titleOf(scope), columns, rows);
});
const refresh = (): void => fields.forEach((field) => field.set());
const mark = (errors: Record<string, KeyError>): void => {
for (const [id, field] of fields) {
const error = errors[id];
field.setError(error ? L(KEY_ERROR_TEXT[error]) : undefined);
}
};
refresh();
return { groups, mark, refresh };
}
+26 -31
View File
@@ -9,6 +9,7 @@
import { ui_locales, currentLanguageIndex, setLanguage } from "@ui/ui_template_locale";
import { createButton } from "@ui/ui_template_elements";
import { createSegment } from "@ui/ui_template_segment";
import { THEME_STORAGE_KEY, LANG_STORAGE_KEY, ROUTES } from "@config/config_frontend";
import { navigateTo, isAuthenticated, setLeaveGuard, takeLeaveGuard } from "./router";
import { readTouchMode, setTouchMode, type TouchMode } from "./device_context";
@@ -132,7 +133,7 @@ function buildBrand(): HTMLButtonElement {
* 서랍을 열 때 서랍으로 옮기고 닫을 때 제자리(진행 판)로 — 그리기 · 누름 코드는 한 벌. */
let drawerSteps: { node: HTMLElement; home: HTMLElement | null } | null = null;
/** 집(진행 판) 없는 요소 = 서랍이 늘 가짐 · 닫을 때 안 옮김(B01 · M01 · M02 바로가기) */
/** 집(진행 판) 없는 요소 = 서랍이 늘 가짐 · 닫을 때 안 옮김(M01 · M02 바로가기 · 기본 목록) */
export function setDrawerSteps(node: HTMLElement | null): void {
drawerSteps = node ? { node, home: node.parentElement } : null;
}
@@ -177,11 +178,20 @@ function buildDrawer(header: HTMLElement): HTMLElement[] {
const setOpen = (open: boolean): void => {
header.classList.toggle("is-drawer-open", open);
burger.setAttribute("aria-expanded", String(open));
if (open && !drawerSteps) void fillDefaultSteps();
if (!drawerSteps) return;
if (open) steps.append(drawerSteps.node);
if (open)
steps.replaceChildren(drawerSteps.node); // 앞서 맡은 기본 목록이 있으면 갈아 끼움
else if (drawerSteps.home && steps.contains(drawerSteps.node))
drawerSteps.home.append(drawerSteps.node);
};
// 진행 판 · 바로가기를 안 맡긴 쪽(772-1) = 기본 목록(🏠 대시보드 + 권한별 M01 · M02) · 늦게 온 쪽 목록이 이김
const fillDefaultSteps = async (): Promise<void> => {
const bar = await import("./m_settings_nav").then((m) => m.defaultSettingsBar());
if (!bar || drawerSteps || !steps.isConnected) return;
setDrawerSteps(bar);
if (header.classList.contains("is-drawer-open")) steps.replaceChildren(bar);
};
burger.addEventListener("click", () => setOpen(!header.classList.contains("is-drawer-open")));
scrim.addEventListener("click", () => setOpen(false));
header.addEventListener("keydown", (e) => e.key === "Escape" && setOpen(false)); // ☰ 에 포커스가 있어도 닫힘
@@ -246,35 +256,23 @@ function buildTouchModeSwitch(): HTMLElement {
box.className = "app-drawer__touch";
const label = document.createElement("span");
label.textContent = L("App_TouchMode");
const group = document.createElement("div");
group.className = "app-drawer__row";
group.setAttribute("role", "radiogroup");
group.setAttribute("aria-label", L("App_TouchMode"));
const modes: [TouchMode, keyof typeof ui_locales][] = [
["auto", "App_TouchMode_Auto"],
["on", "App_TouchMode_On"],
["off", "App_TouchMode_Off"],
];
const buttons = modes.map(([mode, key]) => {
const b = createButton({
label: L(key),
variant: "pill",
onClick: () => {
setTouchMode(mode);
mark();
},
});
b.setAttribute("role", "radio");
b.dataset.touchMode = mode;
return b;
// 부품 「분할 고르기」(36) — 칸마다 data-touch-mode
const group = createSegment({
options: modes.map(([mode, key]) => ({ value: mode, label: L(key) })),
value: readTouchMode(),
size: 36,
ariaLabel: L("App_TouchMode"),
onChange: (mode) => setTouchMode(mode as TouchMode),
});
const mark = (): void => {
const now = readTouchMode();
for (const b of buttons) b.setAttribute("aria-checked", String(b.dataset.touchMode === now));
};
mark();
group.append(...buttons);
box.append(label, group);
group.root.querySelectorAll<HTMLElement>("[data-value]").forEach((b) => {
b.dataset.touchMode = b.dataset.value;
});
box.append(label, group.root);
return box;
}
@@ -496,7 +494,7 @@ html:is([data-burger="on"], .is-bm-route) .app-burger { display: inline-flex; }
.app-drawer__steps .ui-workflow-layout__steps.is-vertical .ui-workflow-layout__step { min-height: var(--spacing-40); margin-inline: 8px; width: auto; padding-left: 7px; }
.app-drawer :is(.ui-btn__label, .ui-workflow-layout__step-label) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app-drawer__steps .ui-workflow-layout__step--home { margin-bottom: 0; }
html[data-touch="on"] :is(.app-drawer__nav > *, .app-drawer__site > summary, .app-drawer__steps .ui-workflow-layout__step, .app-drawer__row > *) { min-height: 44px; }
html[data-touch="on"] :is(.app-drawer__nav > *, .app-drawer__site > summary, .app-drawer__steps .ui-workflow-layout__steps.is-vertical .ui-workflow-layout__step, .app-drawer__row > *) { min-height: 44px; }
.app-drawer__row {
display: flex;
flex-wrap: wrap;
@@ -522,11 +520,8 @@ html[data-touch="on"] :is(.app-drawer__nav > *, .app-drawer__site > summary, .ap
color: var(--color-text-secondary);
font-size: var(--text-caption);
}
.app-drawer__touch .app-drawer__row { padding-inline: 0; }
.app-drawer__touch [aria-checked="true"] {
background: var(--color-mist-violet);
color: var(--color-accent);
}
.app-drawer__touch .ui-segment { display: flex; }
.app-drawer__touch .ui-segment__item { flex: 1 1 0; min-width: 0; }
/* 휴대폰 폭 — 오른쪽 단추(언어 · 테마 · 로그인)는 서랍 안으로 */
html[data-size="compact"] .app-header { padding: 0 var(--spacing-16); gap: var(--spacing-8); }
html[data-size="compact"] .app-actions { display: none; }
+21 -4
View File
@@ -169,6 +169,27 @@ export async function purgeOtherProjects(projectId: string): Promise<void> {
});
}
/** 그 프로젝트 몫만 지운다(401 · 떠날 때) — 다른 탭 · 다른 프로젝트 줄은 둠 · 이 탭 준비 표식도 버림. */
export async function purgeProject(projectId: string): Promise<void> {
if (!projectId) return;
try {
const stamp = window.sessionStorage.getItem(PRELOADED_SIGNATURE_KEY);
if (stamp?.startsWith(`${projectId}|`))
window.sessionStorage.removeItem(PRELOADED_SIGNATURE_KEY);
} catch {
/* 저장소가 막힌 브라우저 — 표식이 없다. */
}
await deleteWhere((store) => {
const cursorRequest = store.index("projectId").openKeyCursor(IDBKeyRange.only(projectId));
cursorRequest.onsuccess = () => {
const cursor = cursorRequest.result;
if (!cursor) return;
store.delete(cursor.primaryKey);
cursor.continue();
};
});
}
/** 보관함에서 바이트만 꺼낸다 — 네트워크를 타지 않는다. 없으면 null.
* 주소에 열쇠가 박히는 자료(3D 코리도)는 이걸로 **먼저 보고** 없을 때만 받는다. */
export async function readCachedBytes(projectId: string, url: string): Promise<ArrayBuffer | null> {
@@ -456,10 +477,6 @@ export async function preloadSurfaceAssets(
),
),
);
// 위성사진은 <img>로 표시하므로 보관함이 아니라 브라우저 자체 캐시를 데워 둔다.
await fetch(`${API_BASE_URL}/projects/${projectId}/vworld-map?layer_name=satellite`, {
credentials: "include",
}).catch(() => null);
report("준비 완료", 1);
return confirmed.signature;
+2 -2
View File
@@ -12,7 +12,6 @@
* ========================================================================== */
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend";
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
import { createButton } from "@ui/ui_template_elements";
import {
@@ -21,6 +20,7 @@ import {
WORKFLOW_STEP_ROUTES,
type WorkflowState,
} from "./b_workflow_nav";
import { currentProjectId } from "./b_project_switch";
/** locale 헬퍼 */
function L(key: keyof typeof ui_locales): string {
@@ -107,7 +107,7 @@ export async function renderPendingWorkflow(
opts: PendingWorkflowOptions,
): Promise<void> {
injectScaffoldStyles();
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
const projectId = currentProjectId();
let workflowState: WorkflowState | undefined;
if (projectId) {
try {
+72 -56
View File
@@ -32,8 +32,6 @@ export interface StateEntry {
scope: StateScope;
/** 형식이 바뀌면 올린다 — 옛 값을 읽지 않고 기본값으로 시작한다. */
version?: number;
/** 옛 키(있으면 한 번 읽어 옮기고 지운다). `project`·`route` 범위는 함수로 받는다. */
legacy?: (projectId?: string, routeId?: number | string) => string;
}
/**
@@ -48,14 +46,13 @@ export const STATE_REGISTRY = {
"profile-collapsed": {
bucket: "pref",
scope: "global",
legacy: () => "b05-route-profile-collapsed",
},
"profile-height": { bucket: "pref", scope: "global", legacy: () => "b05-route-profile-height" },
"profile-height": { bucket: "pref", scope: "global" },
/** B06 위 평면 겹침 패널 높이(436) */
"plan-peek-height": { bucket: "pref", scope: "global" },
/** 배수유역 판 접힘 — 기억 없음 = 닫힘(392-1 · 옛 "false" 기억 버림). */
"drainage-collapsed": { bucket: "pref", scope: "global", version: 2 },
"drainage-width": { bucket: "pref", scope: "global", legacy: () => "b05-route-drainage-width" },
"drainage-width": { bucket: "pref", scope: "global" },
/** M02 면적산출 오른쪽 일위대가 패널(`AreaPanel`) — 접힘 · 폭(옛 조합 패널 키 그대로) */
"m02-combo-panel-collapsed": { bucket: "pref", scope: "global" },
"m02-combo-panel-width": { bucket: "pref", scope: "global" },
@@ -77,26 +74,22 @@ export const STATE_REGISTRY = {
"masshaul-open": {
bucket: "pref",
scope: "global",
legacy: () => "b05-route-profile-masshaul-open",
},
"masshaul-height": {
bucket: "pref",
scope: "global",
legacy: () => "b05-route-profile-masshaul-height",
},
/** 유토곡선 범례 — B05·B06 이 같은 값을 본다(예전에도 키를 공유했다). */
"masshaul-visible": {
bucket: "pref",
scope: "global",
version: 6,
legacy: () => "b06:masshaul-visible-v4",
},
"table-open": { bucket: "pref", scope: "global", legacy: () => "b05:profile:table:open" },
"table-height": { bucket: "pref", scope: "global", legacy: () => "b05:profile:table:height" },
"table-open": { bucket: "pref", scope: "global" },
"table-height": { bucket: "pref", scope: "global" },
"section-panel-collapsed": {
bucket: "pref",
scope: "global",
legacy: () => "b06:profile-panel-collapsed",
},
/** B06 횡단 카드 한 줄 카드 수(138-3 · 1~6 · 기본 3). */
"cross-columns": { bucket: "pref", scope: "global" },
@@ -109,7 +102,6 @@ export const STATE_REGISTRY = {
"section-panel-height": {
bucket: "pref",
scope: "global",
legacy: () => "b06:profile-panel-height",
},
/* ── ② 설계 초안 ─────────────────────────────────────────────────────── */
@@ -120,21 +112,20 @@ export const STATE_REGISTRY = {
/** B06 횡단 [상세] 표 손질(179-4) — 측점키(0.01 m) → 고른 칸 · 더한 행 · 열. [저장]이 내보냄. */
"detail-table": { bucket: "draft", scope: "route" },
/** 아직 정본에 안 넣은 구조물 목록. B05 에서 만들고 B06 [저장]이 내보낸다. */
structures: { bucket: "draft", scope: "project", legacy: (p) => `b05:structures:${p}` },
structures: { bucket: "draft", scope: "project" },
/** 아직 정본에 안 넣은 추가 측점(정수 m 목록 · 368-2) — 정본 `plus_stations.json` · [저장]이 내보냄. */
"plus-stations": { bucket: "draft", scope: "project" },
/** 3D 램프로 바꾼 측점 상단측(측구 방향). */
uphill: { bucket: "draft", scope: "project", legacy: (p) => `b05:uphill:${p}` },
uphill: { bucket: "draft", scope: "project" },
/** B05 배수유역도에서 고친 관 목록(추가·이동·삭제) — 2026-09-06 되살림.
* 예전에는 패널 메모리에만 있어 B06 으로 넘어가면 편집이 사라졌다(대응표 조사).
* 정본은 `pipe_points.json` 이고, 이 초안은 [저장]·[확정] 한 본문(`pipesPart`)이
* 내보낸 뒤 비운다. */
pipes: { bucket: "draft", scope: "project", legacy: (p) => `b05:pipes:${p}` },
pipes: { bucket: "draft", scope: "project" },
/** B05 에서 고른 구조물을 B06 이 이어받는 자리 — 예전 `aislo:structure-pick:*`. */
"structure-pick": {
bucket: "draft",
scope: "project",
legacy: (p) => `aislo:structure-pick:${p}`,
},
/** 관 세션 id 장부(323-5 · `design_pipe_ids`) — `{next, ids: [{id, at}]}` · 저장 안 함. */
"pipe-ids": { bucket: "draft", scope: "project" },
@@ -147,41 +138,36 @@ export const STATE_REGISTRY = {
/** 종류를 바꾼 관 자리 `{누가거리: 처음 종류}`(318-4) — [저장] 때 그 측점 옛 조정값을 정본에서도 비움. */
kindreset: { bucket: "draft", scope: "project" },
/** 배수관·암거 조정창에서 예약한 옵션 값. */
culvertopt: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:culvertopt:${p}:${r}` },
culvertopt: { bucket: "draft", scope: "route" },
/** 암 경계선 오프셋(측점별). */
rockb: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:rockb:${p}:${r}` },
rockb: { bucket: "draft", scope: "route" },
/** 측점별 암 절토 경사비(1:n 의 n) — 카드에서 넣은 사용자 값(2026-09-07).
* 암 경계선(`rockb`)과 같은 자리·같은 꼴이다. 재계산에 **계산 전에** 실어 보내야
* 설계선이 새 경사로 그려진다(값만 베껴 붙이면 그림과 숫자가 어긋난다). */
cutslope: { bucket: "draft", scope: "route" },
/** 소단 제원(측점키 → {width_m, interval_m, slope_deg}) — 계획서 3-9.
* 사용자가 구간에 놓은 값이라 재계산에 함께 실어 보내야 한다. 안 실으면 계획선을
* 고치는 순간 계단이 사라진다(암 경계선이 옛 키를 보던 것과 같은 자리). */
berm: { bucket: "draft", scope: "route" },
/** 표준 횡단면 설정 패널의 편집값.
*
* 칸이 비었으면 `sections/context` 의 저장분(`stored_standard_cross_section`)으로 채운다
* (`B06_Section_Api_Fetch` seedStandardCross) — 같은 응답의 `standard_cross_section` 은
* config 기본값. 고침으로 안 본다 — [폐기]에도 안 지움(`design_leave` NOT_EDITS).
* [초기화] · 추천 노선 [적용]에서 버린다. */
"std-cross": { bucket: "draft", scope: "project", legacy: (p) => `b06:std-cross:${p}` },
"std-cross": { bucket: "draft", scope: "project" },
/** 표시 반폭 — 사용자가 고른 값이라 초안이되, 바꾸면 횡단 재생성(③)을 함께 부른다. */
"cross-display": {
bucket: "draft",
scope: "route",
legacy: (p, r) => `b06:cross-display:${p}:${r}`,
},
/* 측점 조정창이 쌓는 4축·단수·연동 값 — 전부 노선 단위 초안이고 [저장]에서 함께 나간다.
옛 키는 `b06:{이름}:{프로젝트}:{노선}` 한 규칙이었다. */
crossw: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:crossw:${p}:${r}` },
revetx: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:revetx:${p}:${r}` },
revetlink: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:revetlink:${p}:${r}` },
basinadjust: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:basinadjust:${p}:${r}` },
inletstruct: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:inletstruct:${p}:${r}` },
extrawall: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:extrawall:${p}:${r}` },
fordadjust: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:fordadjust:${p}:${r}` },
boxadjust: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:boxadjust:${p}:${r}` },
extraspan: { bucket: "draft", scope: "route", legacy: (p, r) => `b06:extraspan:${p}:${r}` },
crossw: { bucket: "draft", scope: "route" },
revetx: { bucket: "draft", scope: "route" },
revetlink: { bucket: "draft", scope: "route" },
basinadjust: { bucket: "draft", scope: "route" },
inletstruct: { bucket: "draft", scope: "route" },
extrawall: { bucket: "draft", scope: "route" },
fordadjust: { bucket: "draft", scope: "route" },
boxadjust: { bucket: "draft", scope: "route" },
extraspan: { bucket: "draft", scope: "route" },
/** 횡단 카드 버튼(지반유형·단면유형·측구·포장·2단 비탈) 선택 — 2026-09-06.
* 예전에는 버튼을 누를 때마다 서버가 계산해 **바로 저장**했다. 조작은 캐시에 쌓고
* [저장]·[확정]에서만 정본으로 나가야 한다(사용자 확정). */
@@ -198,8 +184,10 @@ export const STATE_REGISTRY = {
"b08-options": { bucket: "draft", scope: "project" },
/** B08 거리이정표 입력 `{site, base, routes}` 한 벌 — [저장] 때 프로젝트 B08_Quantity/milestone 으로(244-1). */
"b08-milestone": { bucket: "draft", scope: "project" },
/** D03 예정노선 분석 `{nodes: 끈 지점 P0…Pn | null, choices: {연도: 갈래}}` — [저장] 때 plan/analysis 로(530-3). */
/** D03 예정노선 노선분석 `{version: 2, network: 그은 선 · 갈래 · 조각}` — [저장] 때 plan/network 로(563-1). */
"d03-plan": { bucket: "draft", scope: "project" },
/** D03 예정노선 분석 지형 원천 `"sheet" | "lidar"`(624-4 · 지형 칩) — 화면 캐시만 · 서버로 안 감. */
"d03-terrain": { bucket: "draft", scope: "project" },
/** B09 원가 사용자 값 `{materials, estimate}`(고친 칸만) — [저장] 때 프로젝트 B09 폴더로(144). */
"b09-estimation": { bucket: "draft", scope: "project" },
/** 미저장 계획고 편집(▲▼ · 곡선반경) — 설계 가게 `alignmentDraft` 의 칸(계획서 24-2c).
@@ -214,10 +202,17 @@ export const STATE_REGISTRY = {
"route-draft-hold": { bucket: "draft", scope: "project" },
/** D04 연도별 지정 업체 목록(531-3) — [저장] 때 PUT plan/assignees. */
"d04-assignees": { bucket: "draft", scope: "project" },
/* E 페이지 캐시(401) — 진입 · 떠남 비우기가 같은 표를 봄 · 옛 키는 한 번 옮김. */
"e02-ground": { bucket: "draft", scope: "project" },
"e03-prob": { bucket: "draft", scope: "project" },
"e04-settings": { bucket: "draft", scope: "project" },
"e05-report": { bucket: "draft", scope: "project" },
"e06-rock": { bucket: "draft", scope: "project" },
"e07-settings": { bucket: "draft", scope: "project" },
/* ── ④ 계산 결과 ─────────────────────────────────────────────────────── */
/** 노선·종단 최신 응답. 페이지를 오갈 때 이 값으로 먼저 그린다. */
latest: { bucket: "result", scope: "project", legacy: (p) => `b05:latest:${p}` },
latest: { bucket: "result", scope: "project" },
/** 종횡단 설정값(config 상수 + 프로젝트 임도 종류 + 노선 id). B05·B06 이 같은 값을
* 받아 쓰므로 화면을 오갈 때마다 다시 묻지 않는다(2026-09-06 호출 정리).
* 노선이 바뀌면 `latest` 와 함께 버린다. */
@@ -228,13 +223,11 @@ export const STATE_REGISTRY = {
"std-cross-default": {
bucket: "result",
scope: "project",
legacy: (p) => `b06:std-cross-default:${p}`,
},
/** 서버가 준 암 경계선 기본 오프셋 — 위와 같은 성격. */
"rock-boundary-default": {
bucket: "result",
scope: "project",
legacy: (p) => `b06:rock-boundary-default:${p}`,
},
/** B05 계획노선 편집 창 — 마지막에 적용한 초기노선 갈래(로직 · 비중 · 노드 · 비교값).
* 노선을 다시 계산해 결과를 버릴 때도 이 값은 새 노선의 출발점이라 되살린다(`_Apply`). */
@@ -430,6 +423,20 @@ export function onStateRemoved(fn: (key: string) => void): () => void {
return () => removedListeners.delete(fn);
}
/** 세션 칸을 골라 지움(401 떠남) — 칸마다 지움 알림(설계 가게가 미룬 쓰기를 거둠). */
export function clearSessionKeys(match: (key: string) => boolean): void {
const doomed: string[] = [];
try {
for (let index = 0; index < window.sessionStorage.length; index += 1) {
const key = window.sessionStorage.key(index);
if (key !== null && match(key)) doomed.push(key);
}
} catch {
return; // 저장소가 막힌 브라우저 — 지울 것이 없다.
}
doomed.forEach((key) => writeRaw(key, null));
}
function writeRaw(key: string, value: string | null): void {
if (value === null) removedListeners.forEach((fn) => fn(key));
try {
@@ -465,20 +472,6 @@ function setOrMakeRoom(store: Storage, key: string, value: string): void {
}
}
/** 옛 키에 남은 값을 새 키로 한 번 옮긴다(옮기고 나면 옛 키는 지운다). */
function migrate(
name: StateName,
key: string,
projectId?: string | null,
routeId?: number | string | null,
): void {
const legacy = entryOf(name)?.legacy?.(projectId ?? undefined, routeId ?? undefined);
if (!legacy || legacy === key) return;
const old = readRaw(legacy);
if (old !== null && readRaw(key) === null) writeRaw(key, old);
if (old !== null) writeRaw(legacy, null);
}
/** 문자열 그대로 읽는다(숫자·참거짓처럼 JSON 이 아닌 값). */
export function readStateRaw(
name: StateName,
@@ -487,7 +480,6 @@ export function readStateRaw(
): string | null {
const key = stateKey(name, projectId, routeId);
if (!key) return null;
migrate(name, key, projectId, routeId);
return readRaw(key);
}
@@ -604,8 +596,32 @@ export function clearResults(projectId: string | null, routeId?: number | string
* 값이 빈 객체(`{}`)면 없는 것으로 본다.
*/
export function hasDrafts(projectId: string | null, routeId?: number | string | null): boolean {
return namesInBucket("draft").some((name) => {
const raw = readStateRaw(name, projectId, routeId);
return raw !== null && raw !== "{}" && raw !== "[]" && raw !== "";
});
return namesInBucket("draft").some((name) =>
draftFilled(readStateRaw(name, projectId, routeId), name, projectId),
);
}
/** 저장분이 비어 있지 않은 목록 칸(관 · 구조물) — 페이지가 저장분을 받을 때 적음(469-3 · 새 요청 없음). */
const savedLists = new Set<string>();
export function noteSavedList(projectId: string, name: StateName, nonEmpty: boolean): void {
const key = `${name}:${projectId}`;
if (nonEmpty) savedLists.add(key);
else savedLists.delete(key);
}
export function forgetSavedLists(projectId: string): void {
for (const key of [...savedLists]) if (key.endsWith(`:${projectId}`)) savedLists.delete(key);
}
/**
* 초안 칸이 고침인가 — 떠남 확인(`design_leave`)과 `hasDrafts` 가 쓰는 한 규칙.
* 칸 없음 · "" · "{}" = 빈 칸 · "[]" = 저장분이 비어 있지 않은 목록 칸일 때만 고침(모두 지움 ≠ 저장분).
*/
export function draftFilled(
raw: string | null,
name: StateName,
projectId: string | null,
): boolean {
if (raw === null || raw === "" || raw === "{}") return false;
if (raw === "[]") return !!projectId && savedLists.has(`${name}:${projectId}`);
return true;
}
+46
View File
@@ -0,0 +1,46 @@
/* =============================================================================
* b_page_tabs.ts
* 페이지 탭 줄 — 부품 「탭」(`ui_template_tabs`)을 이미 있는 줄 요소 안에 채움(553-3 · B08 · B09 · ☰ [설정]).
*
* 쓰기 = `fillTabs(줄, { tabs, value, onChange, kind: "pill", data: "b08Tab" })` — 칸마다 `data-<data>` = 탭 열쇠
* 줄 요소는 페이지가 정함(클래스 · 자리) · 칸 모양 · 크기 · 고름 표시는 부품 몫 · 누른 칸이 지금 칸이면 안 부름
* 탭 이름이 바뀌면 다시 부르면 됨(줄을 통째로 다시 채움)
* ========================================================================== */
import type { UiSize } from "@ui/ui_template_button";
import { createTabs, type TabItem } from "@ui/ui_template_tabs";
export interface PageTabsOptions {
tabs: TabItem[];
value: string;
onChange: (key: string) => void;
/** line = 위 탭(밑줄) · pill = 보조 탭(작은 알약) */
kind?: "line" | "pill";
size?: UiSize;
/** 칸에 붙일 data 이름(camelCase · 예: b08Tab → data-b08-tab) */
data: string;
/** 칸 풍선 */
title?: (key: string) => string | undefined;
}
export function fillTabs(host: HTMLElement, opts: PageTabsOptions): HTMLElement[] {
const tabs = createTabs({
tabs: opts.tabs,
value: opts.value,
kind: opts.kind,
size: opts.size,
onChange: (key) => key !== opts.value && opts.onChange(key),
});
const buttons = [...tabs.root.querySelectorAll<HTMLElement>("[data-tab]")];
for (const button of buttons) {
const key = button.dataset.tab ?? "";
button.dataset[opts.data] = key;
const title = opts.title?.(key);
if (title) button.title = title;
}
host.classList.add(...tabs.root.classList);
host.dataset.size = tabs.root.dataset.size ?? "";
host.setAttribute("role", "tablist");
host.replaceChildren(...tabs.root.childNodes);
return buttons;
}
+157 -16
View File
@@ -1,25 +1,71 @@
/* =============================================================================
* A00_Common/b_project_switch.ts
* 다른 프로젝트에 들어갈 때 **그 밖 프로젝트의 캐시**를 지운다(계획서 290-1 · 요구 183-1).
* 프로젝트마다 따로 동작(401) — 탭마다 지금 프로젝트 · 떠남 물음 · 떠날 때 · 들 때 비움.
*
* 지우는 것 — 이 탭 세션의 다른 프로젝트 칸(초안 · 결과 · 고름 · 옛 키 · 페이지별 칸)과
* 3D 지형 보관함(IndexedDB). 남기는 것 — 지금 프로젝트 것 · 계정 취향(pref, localStorage).
* 다른 프로젝트에 저장 안 한 고침(초안)이 있으면 확인창 — 「남기기」면 그 초안만 둔다.
* 같은 프로젝트로 다시 들어오면(새로고침 포함) 아무것도 안 지운다(탭마다 들어온 표식).
* 지금 프로젝트 = 탭 세션 칸 먼저 · 없으면 마지막으로 연 프로젝트(localStorage · 새 탭이 이어받음).
* 들 때(`enterProject`) — 다른 프로젝트 세션 칸 · 3D 보관함을 지움(290-1) · 저장 안 한 고침이 있으면 확인창.
* 떠날 때(`leaveProject`) — 그 프로젝트 세션 칸 · 준비 표식 · 3D 보관함 몫 · 탭 프로젝트 표식 · 취향은 남김.
* 다른 프로젝트 화면을 그리려 하면 새로 엶(`reloadIfOtherProject` · 모듈 메모리 통째 비움).
* 떠남 물음(`guardProjectLeave`) — 화면에 붙은 가드를 모아 [취소 · 폐기 · 저장] 한 번.
* 화면 모듈은 부를 때만 들임 · 라우터는 스스로 이어 줌(`linkRouter`) — 설계 가게 · 인쇄 묶음이 이 파일을 부름.
* ========================================================================== */
import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend";
import { showConfirmDialog } from "@ui/ui_template_elements";
import { purgeOtherProjects } from "./b_asset_cache";
import { STATE_REGISTRY, type StateEntry } from "./b_page_state";
import { CURRENT_PROJECT_ID_KEY } from "../config/config_frontend";
import { clearSessionKeys, STATE_REGISTRY, type StateEntry } from "./b_page_state";
/** 이 탭이 마지막으로 들어온 프로젝트. */
const ENTERED_KEY = "aislo:entered-project";
const MARK_P = "\u0001";
const MARK_R = "\u0002";
/** 이 탭의 지금 프로젝트(401) — 탭 세션 칸 먼저 · 없으면 마지막으로 연 프로젝트(새 탭) · 없으면 "". */
export function currentProjectId(): string {
try {
return (
window.sessionStorage.getItem(ENTERED_KEY) ||
window.localStorage.getItem(CURRENT_PROJECT_ID_KEY) ||
""
);
} catch {
return "";
}
}
/** 프로젝트를 가르는 키 머리 — 등록표 키(`aislo:{통}:{이름}:`)와 옛 키 머리. 초안 여부를 함께. */
/** 지금 프로젝트로 적음 — 이 탭 + 새 탭이 이어받을 마지막 값. 다른 프로젝트 캐시 비움은 `enterProject`. */
export function setCurrentProject(projectId: string): void {
try {
window.sessionStorage.setItem(ENTERED_KEY, projectId);
window.localStorage.setItem(CURRENT_PROJECT_ID_KEY, projectId);
} catch {
/* 저장소가 막힌 브라우저 — 기억 못 함. */
}
}
/** 지워진 · 권한 없는 프로젝트를 잊음 — 이 탭 표식 · 마지막 값이 그 프로젝트일 때만. */
export function forgetProject(projectId: string): void {
try {
if (window.sessionStorage.getItem(ENTERED_KEY) === projectId)
window.sessionStorage.removeItem(ENTERED_KEY);
if (window.localStorage.getItem(CURRENT_PROJECT_ID_KEY) === projectId)
window.localStorage.removeItem(CURRENT_PROJECT_ID_KEY);
} catch {
/* 저장소가 막힌 브라우저 — 잊을 것이 없다. */
}
}
/** 이 화면(모듈 메모리)이 담은 프로젝트 — 프로젝트 화면을 그릴 때마다 견줌. */
let heldProject = "";
/** 다른 프로젝트 화면을 그리려 하면 새로 엶(401 · 모듈 메모리 통째 비움) · true = 새로 여는 중. */
export function reloadIfOtherProject(): boolean {
const projectId = currentProjectId();
if (heldProject && projectId && projectId !== heldProject) {
window.location.reload();
return true;
}
if (projectId) heldProject = projectId;
return false;
}
/** 프로젝트를 가르는 키 머리 — 등록표 키(`aislo:{통}:{이름}:`). 초안 여부를 함께. */
function projectPrefixes(): Array<{ prefix: string; draft: boolean }> {
const out: Array<{ prefix: string; draft: boolean }> = [];
Object.entries(STATE_REGISTRY as Record<string, StateEntry>).forEach(([name, entry]) => {
@@ -27,9 +73,6 @@ function projectPrefixes(): Array<{ prefix: string; draft: boolean }> {
const draft = entry.bucket === "draft";
const suffix = entry.version && entry.version > 1 ? `-v${entry.version}` : "";
out.push({ prefix: `aislo:${entry.bucket}:${name}${suffix}:`, draft });
const legacy = entry.legacy?.(MARK_P, MARK_R);
const at = legacy?.indexOf(MARK_P) ?? -1;
if (legacy && at > 0) out.push({ prefix: legacy.slice(0, at), draft });
});
return out;
}
@@ -107,6 +150,7 @@ export async function enterProject(
const keep = new Set<string>();
for (const other of plan.draftProjects) {
const name = await nameOf(other).catch(() => other);
const { showConfirmDialog } = await import("@ui/ui_template_elements");
const drop = await showConfirmDialog(
`「${name}」에 저장 안 한 고침이 있음 — 지우고 열기 / 남기기`,
"지우고 열기",
@@ -115,7 +159,8 @@ export async function enterProject(
if (!drop) keep.add(other);
}
plan.doomed(keep).forEach((key) => window.sessionStorage.removeItem(key));
window.sessionStorage.setItem(ENTERED_KEY, projectId);
setCurrentProject(projectId);
const { purgeOtherProjects } = await import("./b_asset_cache");
await purgeOtherProjects(projectId);
} catch {
/* 저장소가 막힌 브라우저 — 캐시를 못 지워도 프로젝트는 연다. */
@@ -136,6 +181,7 @@ export async function wipeAccountCache(): Promise<void> {
} catch {
/* 저장소가 막힌 브라우저 — 지울 것이 없다. */
}
const { purgeOtherProjects } = await import("./b_asset_cache");
await purgeOtherProjects(""); // 빈 id 보다 큰 열쇠 = 모든 프로젝트
}
@@ -153,3 +199,98 @@ export async function wipeIfOwnerChanged(user: string | number): Promise<boolean
await wipeAccountCache();
return true;
}
/**
* 프로젝트를 떠남(401) — 이 탭 세션의 그 프로젝트 칸 · 준비 표식 · 3D 보관함 몫을 지움.
* 탭 프로젝트 표식(뒤로 가기 · 재진입) · 취향 · 입력 설정 · CAD 복구본(localStorage)은 남김.
*/
export async function leaveProject(projectId: string): Promise<void> {
if (!projectId) return;
// 프로젝트 칸 열쇠는 모두 프로젝트 번호(uuid)를 품음 — 등록표 · 옛 키 · 페이지 칸을 한 줄로.
// 등록표 창구로 지움 — 지움 알림을 받은 설계 가게가 미뤄 둔 쓰기(1 s)를 거둠(떠난 뒤 되살아남 막음).
// ponytail: 아직 한 번도 안 쓴 칸의 미룬 쓰기는 못 거둠 — 생기면 가게에 프로젝트 단위 거둠을 더함.
clearSessionKeys((key) => key.includes(projectId));
const { purgeProject } = await import("./b_asset_cache");
await purgeProject(projectId);
}
/** 페이지가 알리는 떠남 셋(401) — 고침 있나 · 저장(false · 던짐 = 머묾) · 폐기. */
export interface ProjectLeaveGuard {
dirty: () => boolean;
save: () => Promise<unknown>;
discard?: () => void | Promise<void>;
/** 물음 글 — 없으면 공용 */
message?: string;
/** 같은 페이지로 보는 주소(B05 ↔ B06) — 그리로 가면 안 묻고 다시 걺 */
samePage?: readonly string[];
}
/** 라우터의 떠남 물음 자리 — 라우터가 들 때 이어 줌(라우터 → 이 파일 한 방향 · 페이지 표를 안 끌게). */
interface RouterLink {
currentRoute: () => string;
setLeaveGuard: (guard: (() => Promise<boolean>) | null) => void;
}
let routerLink: RouterLink | null = null;
export function linkRouter(link: RouterLink): void {
routerLink = link;
}
const guards: Array<{ root: HTMLElement; guard: ProjectLeaveGuard }> = [];
let unloadHooked = false;
/** 화면에 붙은 가드만 — 빠진 페이지 것은 버림. */
function liveGuards(): ProjectLeaveGuard[] {
for (let index = guards.length - 1; index >= 0; index -= 1) {
if (!guards[index].root.isConnected) guards.splice(index, 1);
}
return guards.map((entry) => entry.guard);
}
/** 저장 안 한 고침이 있나 — 화면에 붙은 가드 모두. */
export const projectLeaveDirty = (): boolean => liveGuards().some((guard) => guard.dirty());
/** 떠나도 되나 — 고친 가드를 모아 [취소 · 폐기 · 저장] 한 번 · true = 넘어감. */
export async function askProjectLeave(): Promise<boolean> {
const live = liveGuards();
if (!live.length) return true;
const route = routerLink?.currentRoute() ?? "";
if (live.some((guard) => guard.samePage?.includes(route))) {
// 라우터가 「넘어감」 뒤 물음을 내림 — 같은 페이지 안 이동이면 한 틱 뒤 다시 걺.
window.setTimeout(() => liveGuards().length && routerLink?.setLeaveGuard(askProjectLeave), 0);
return true;
}
const dirty = live.filter((guard) => guard.dirty());
if (!dirty.length) return true;
const [{ leaveOrStay }, { t }] = await Promise.all([
import("@ui/ui_template_leave_dialog"),
import("@ui/ui_template_locale"),
]);
const message = (dirty.length === 1 && dirty[0].message) || t("Common_Leave_Unsaved");
const got = await leaveOrStay(message, {
save: async () => {
for (const guard of dirty) {
if (guard.dirty() && (await guard.save()) === false) throw new Error("저장 못 함");
}
},
stillDirty: () => dirty.some((guard) => guard.dirty()),
discard: async () => {
for (const guard of dirty) await guard.discard?.();
},
});
return got !== "stay";
}
/** 페이지를 세울 때 한 번 — `root` 가 화면에서 빠지면 스스로 풂 · 탭 닫기도 물음. */
export function guardProjectLeave(root: HTMLElement, guard: ProjectLeaveGuard): void {
liveGuards(); // 빠진 옛 페이지를 붙들지 않음
guards.push({ root, guard });
if (!unloadHooked) {
unloadHooked = true;
window.addEventListener("beforeunload", (event) => {
if (!projectLeaveDirty()) return;
event.preventDefault();
event.returnValue = "";
});
}
routerLink?.setLeaveGuard(askProjectLeave);
}
+11 -5
View File
@@ -15,7 +15,7 @@
* 그리는 일은 안 한다 — 넘겨받은 `layer` 에 `transform` 만 적는다.
* ========================================================================== */
import { wheelEventIn } from "./input_prefs";
import { isPanButton, swallowNextMenu, wheelEventIn } from "./input_prefs";
/** 줌·팬 상태 — 다시 그릴 때 배율을 되살리는 데 쓴다(2026-08-22 사용자 ③). */
export interface ZoomPanState {
@@ -75,7 +75,7 @@ export interface SvgZoomPanOptions {
/** 휠을 줌에 묶을지(기본 안 묶음). 묶으면 **커서 아래 지점**을 붙잡고 확대한다.
* `"ctrl"` = Ctrl+휠만 줌 · 맨 휠은 페이지 스크롤에 둠. */
wheelZoom?: boolean | "ctrl";
/** 팬을 시작할 누름인지 — 안 주면 가운데 버튼만. 고르기가 없는 빈 곳 왼쪽 끌기를 더할 때 쓴다. */
/** 팬을 시작할 누름인지 — 안 주면 ☰ [설정] 옮기기 버튼(가운데는 늘). 고르기가 없는 빈 곳 왼쪽 끌기를 더할 때 쓴다. */
panStart?: (event: PointerEvent) => boolean;
}
@@ -166,9 +166,14 @@ export function attachSvgZoomPan(options: SvgZoomPanOptions): ZoomPanHandle {
svg.addEventListener("pointerdown", (event) => {
// 누름마다 풂 — 가운데 단추 팬 뒤엔 click 이 안 와 `moved` 가 남아 왼쪽 누름을 삼켰음(259-1)
moved = false;
// 팬은 **가운데 버튼**만(기본). 좌클릭은 그 화면의 고르기 몫이다.
if (!(options.panStart ? options.panStart(event) : event.button === 1)) return;
event.preventDefault();
// 팬 = ☰ [설정] 옮기기 버튼(가운데는 늘 · 588-4) · 왼쪽이면 안 도형이 잡은 누름은 비킴 · 끈 뒤 click 은 아래서 막음
const claimed =
event.button === 0 &&
(event.defaultPrevented ||
(event.target as Element | null)?.hasPointerCapture?.(event.pointerId));
if (!(options.panStart ? options.panStart(event) : isPanButton(event.button) && !claimed))
return;
if (event.button !== 0) event.preventDefault(); // 왼쪽은 안 도형의 mousedown 을 살림
panning = true;
moved = false;
lastX = event.clientX;
@@ -191,6 +196,7 @@ export function attachSvgZoomPan(options: SvgZoomPanOptions): ZoomPanHandle {
const endPan = (event: PointerEvent): void => {
if (!panning) return;
panning = false;
if (moved && event.button === 2) swallowNextMenu(); // 오른쪽 끌어 옮김 뒤 메뉴 안 뜸
svg.classList.remove("is-panning");
try {
svg.releasePointerCapture(event.pointerId);
+10 -10
View File
@@ -1,9 +1,4 @@
import {
API_BASE_URL,
CURRENT_PROJECT_ID_KEY,
ROUTES,
type RoutePath,
} from "@config/config_frontend";
import { API_BASE_URL, ROUTES, type RoutePath } from "@config/config_frontend";
import {
hideLoadingOverlay,
setLoadingOverlayProgress,
@@ -16,7 +11,12 @@ import {
fetchSurfaceStatus,
} from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
import { isProjectPreloaded, setPreloadTarget } from "./b_asset_cache";
import { enterProject } from "./b_project_switch";
import {
currentProjectId,
enterProject,
forgetProject,
setCurrentProject,
} from "./b_project_switch";
import { navigateTo } from "./router";
export interface WorkflowState {
@@ -77,7 +77,7 @@ export async function fetchWorkflowState(projectId: string): Promise<WorkflowSta
/** 기억한 현재 프로젝트가 서버에 없으면(지워짐 · 권한 없음) 기억을 비우고 true.
* 그 밖의 실패(서버 오류 · 끊김)는 false — 기억을 지우지 않고 페이지가 하던 대로 둔다. */
export async function dropGoneProject(): Promise<boolean> {
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
const projectId = currentProjectId();
if (!projectId) return false;
try {
await fetchWorkflowState(projectId);
@@ -85,7 +85,7 @@ export async function dropGoneProject(): Promise<boolean> {
} catch (error) {
const status = (error as { status?: number }).status;
if (status !== 403 && status !== 404) return false;
localStorage.removeItem(CURRENT_PROJECT_ID_KEY);
forgetProject(projectId);
return true;
}
}
@@ -105,7 +105,7 @@ export function goToWorkflowStage(projectId: string, route: RoutePath): Promise<
projectId,
async (id) => (await fetchWorkflowState(id)).project_name || id,
).then(() => {
localStorage.setItem(CURRENT_PROJECT_ID_KEY, projectId);
setCurrentProject(projectId);
// 단계가 바뀌어 이동하는 자리다 — 캐시를 버려 다음 화면이 새 상태를 본다.
clearWorkflowStateCache();
if (!PRELOAD_REQUIRED_ROUTES.includes(route)) {
+66
View File
@@ -0,0 +1,66 @@
/* 공용 단가산출 계산서(714-2) — 장비 · 하위 로직 한 줄 = 누르면 펼침 */
[data-sheet-equip] {
cursor: pointer;
border-radius: var(--radius-sm);
}
[data-sheet-equip]:hover,
[data-sheet-equip]:focus-visible {
background: var(--color-hover);
}
[data-sheet-part] .ui-calc-row__label {
padding-left: var(--spacing-16);
}
p[data-sheet-part] {
margin: 0;
padding-left: var(--spacing-16);
}
/* 737-1 옵션 칸 격자 — 상자 폭 따라 1 → 2 → 3 → 4 → 6 열(300 · 450 · 600 · 900) · 칸마다 이름 위 · 고르기 아래 */
.calc-sheet__opts {
container-type: inline-size;
}
.calc-sheet__opts-grid {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--spacing-8);
}
@container (min-width: 300px) {
.calc-sheet__opts-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@container (min-width: 450px) {
.calc-sheet__opts-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@container (min-width: 600px) {
.calc-sheet__opts-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@container (min-width: 900px) {
.calc-sheet__opts-grid {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
}
.calc-sheet__opts-grid > .ui-calc-row {
grid-template-columns: minmax(0, 1fr);
align-items: stretch;
gap: var(--spacing-4);
}
.calc-sheet__opts-grid > .ui-calc-row > :is(.ui-calc-row__select, .ui-calc-row__in, .ui-field) {
grid-column: 1;
justify-self: stretch;
width: 100%;
}
+302
View File
@@ -0,0 +1,302 @@
/* =============================================================================
* calc_sheet.ts
* 공용 「단가산출 계산서」(PLAN 714-2 · 요구서 내역12 4장 5 · 6) — 704-2 M02 계산서 보기를 올린 것.
* 쓰는 곳 = M01 「인력·재료·경비」 보기 · M02 기준 목록 계산서 보기(704-2) · (뒤에) 일위대가 모달 · B09 단가산출근거
* 꼴 둘 —
* 비목(기본 · 704-2) = 비목 묶음마다 식 줄(이름 | 단가 × 품 | 금액) · 소계 · 맨 아래 계
* 묶음(`bundle`) = 인력 · 재료 · 경비(장비) · 하위 — 줄의 `묶음`(714-1 서버 덧칸) 으로 다시 묶음 ·
* 장비 · 하위 로직 = 한 줄(노 · 재 · 경 칸 · 합) + 누르면 펼침(그 줄의 노 · 재 · 경 식) ·
* 묶음 머리 = 이름 · 기준단위 · 환산 근거 · 그 묶음이 쓰는 입력 칸(`쓰는입력`) · 맨 아래 비목 소계 셋 + 계
* 값 = 서버 셈 그대로(묶음 소계만 서버 줄 금액의 합) · 변수 칸 바꾸면 `onInput(이름, 값)` — 부르는 쪽이 다시 셈
* 부품 = `ui_template_calc_row`(값 줄 · 식 줄) · 입력 · 고르기 · 글 — 날 단추 · 표 · 페이지 CSS 없음
* ========================================================================== */
import { createCalcFormula, createCalcFormulaRow, createCalcRow } from "@ui/ui_template_calc_row";
import { el } from "@ui/ui_template_elements";
import { createInputField, createSelectField } from "@ui/ui_template_input";
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { createText } from "@ui/ui_template_text";
import { formatLineMoney, formatMoney } from "../common_util/common_util_money";
import "./calc_sheet.css";
export interface SheetLine {
이름: string;
/** 서버 값 식 「단가 * 품 = 금액」 — 「= 금액」 은 떼고 「*」 → 「×」 */
글: string;
금액: number | null;
/** 714-1 서버 덧칸 — 그 줄 값이 쓰는 로직 입력 · 묶음 · 장비 · 찾는 표 기준 · 환산 근거 */
쓰는입력?: string[];
묶음?: string | null;
장비?: boolean | null;
기준단위?: string | null;
환산근거?: string;
/** (753-1) 옵션으로 빠진 줄 — 그리지 않음(소계 · 쓰는 입력 칸은 그대로) */
옵션꺼짐?: boolean;
/** 714-1b 장비 줄 펼침 — 부른 기계경비 로직의 시간당 줄(조종원 · 주연료 · 잡재료비 · 기계손료) */
펼침?: SheetUnfold | null;
}
export interface SheetUnfold {
키: string;
이름: string;
단위: string;
줄: { 비목: string; 이름: string; 글: string; 금액: number | null }[];
계: number | null;
}
export interface SheetGroup {
비목: string;
소계: number | null;
끝수?: string;
줄: SheetLine[];
}
export interface SheetInput {
이름: string;
단위?: string;
고르기?: (string | number)[];
}
export interface SheetOptions {
묶음: SheetGroup[];
계: number | null;
/** 맨 아래 계 줄 이름(「계 (원/㎡)」) */
sumLabel: string;
/** 인력 · 재료 · 경비(장비) 묶음으로 다시 묶음(없으면 서버 비목 묶음 그대로 · 704-2) */
bundle?: boolean;
/** 변수 칸 — 입력 꼴 · 지금 값 · 바꾼 뒤 할 일(없으면 변수 칸 없음) · 칸 = 묶음 줄들의 `쓰는입력` */
inputs?: SheetInput[];
values?: Record<string, string>;
onInput?: (name: string, value: string) => void;
}
/* ── 순수 셈 ── */
/** 값 식 → 식 칸 글 — 「268,908 * 0.12 = 32,269」 → 「268,908 × 0.12」 */
export const sheetFormula = (text: string): string =>
text.replace(/ = [^=]*$/, "").replaceAll(" * ", " × ");
/** 장비 · 하위 로직 한 줄 — 비목마다 몫 줄(서버 줄 그대로) · 합 */
export interface SheetMerged {
이름: string;
몫: { 비목: string; 줄: SheetLine }[];
금액: number | null;
}
export interface SheetBundle {
묶음: string;
줄: (SheetLine | SheetMerged)[];
소계: number;
쓰는입력: string[];
기준단위: string[];
환산근거: string[];
}
const ORDER = ["인력", "재료", "경비", "하위"];
const OF_COST: Record<string, string> = { 노무비: "인력", 재료비: "재료", 경비: "경비" };
const add = (a: number | null, b: number | null): number | null =>
a === null && b === null ? null : (a ?? 0) + (b ?? 0);
const merges = (l: SheetLine): boolean => !!l.장비 || l.묶음 === "하위";
/** 비목 묶음 → 인력 · 재료 · 경비 · 하위 묶음 — 장비 · 하위 줄은 비목마다 갈린 몫을 한 줄로
* (같은 이름 k 번째끼리 짝 — 서버 줄 차례 = 호표 차례 · 비목마다 같은 차례) */
export function bundleLines(groups: SheetGroup[]): SheetBundle[] {
const bundles = new Map<string, SheetBundle>();
const merged = new Map<string, SheetMerged>();
const take = (name: string): SheetBundle => {
let b = bundles.get(name);
if (!b) {
b = { 묶음: name, 줄: [], 소계: 0, 쓰는입력: [], 기준단위: [], 환산근거: [] };
bundles.set(name, b);
}
return b;
};
for (const g of groups) {
const seen = new Map<string, number>();
for (const line of g.줄) {
const b = take(line.묶음 || OF_COST[g.비목] || g.비목);
b.소계 += line.금액 ?? 0;
for (const n of line.쓰는입력 ?? []) if (!b.쓰는입력.includes(n)) b.쓰는입력.push(n);
if (line.기준단위 && !b.기준단위.includes(line.기준단위)) b.기준단위.push(line.기준단위);
if (line.환산근거 && !b.환산근거.includes(line.환산근거)) b.환산근거.push(line.환산근거);
if (!merges(line)) {
b.줄.push(line);
continue;
}
const k = seen.get(line.이름) ?? 0;
seen.set(line.이름, k + 1);
const id = `${b.묶음}\n${line.이름}\n${k}`;
let m = merged.get(id);
if (!m) {
m = { 이름: line.이름, 몫: [], 금액: null };
merged.set(id, m);
b.줄.push(m);
}
m.몫.push({ 비목: g.비목, 줄: line });
m.금액 = add(m.금액, line.금액);
}
}
const rank = (x: string): number => (ORDER.includes(x) ? ORDER.indexOf(x) : ORDER.length);
return [...bundles.values()].sort((a, b) => rank(a.묶음) - rank(b.묶음));
}
/* ── 순수 셈 끝 ── */
const TEXT = {
인력: ["인력", "Labor"],
재료: ["재료", "Material"],
경비: ["경비(장비)", "Expense (equipment)"],
하위: ["하위 로직", "Sub logic"],
Basis: ["기준", "Basis"],
Short: ["노 {a} · 재 {b} · 경 {c}", "L {a} · M {b} · E {c}"],
Fold: ["누르면 펼침", "Click to expand"],
Per: ["{n} — {v} 원/{u}", "{n} — {v} KRW/{u}"],
} as const satisfies Record<string, readonly [string, string]>;
const t = (k: keyof typeof TEXT): string => TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0];
const bundleName = (b: string): string => (b in TEXT ? t(b as keyof typeof TEXT) : b);
const won = (v: number | null, digits = 0): string =>
v === null ? "-" : digits ? formatLineMoney(v) : formatMoney(v);
const out = (v: number | null, key = false, digits = 0): HTMLElement =>
el("span", { className: `ui-calc-row__out${key ? " is-key" : ""}`, text: won(v, digits) });
const lineRow = (line: SheetLine, label = line.이름): HTMLElement =>
createCalcFormulaRow(label, createCalcFormula(sheetFormula(line.글)), out(line.금액, false, 1));
/** 변수 칸 하나 — 고르기 있으면 고르기(값 없으면 빈 첫 칸) · 아니면 글 칸(Enter · 떠남 때 넘김) ·
* 722-1 공용 단가산출 상세 박스 옵션도 씀 */
export function varRow(spec: SheetInput, value: string, onInput: (v: string) => void): HTMLElement {
let control: HTMLElement;
if (spec.고르기?.length) {
control = createSelectField({
options: [...(value ? [] : [""]), ...spec.고르기.map(String)].map((o) => ({
value: o,
text: o,
})),
value,
compact: true,
ariaLabel: spec.이름,
onChange: onInput,
}).root;
} else {
const box = createInputField({ value, compact: true });
box.input.setAttribute("aria-label", spec.이름);
box.input.addEventListener("change", () => {
if (box.input.value.trim() && box.input.value !== value) onInput(box.input.value.trim());
});
control = box.root;
}
const row = createCalcRow(spec.단위 ? `${spec.이름} (${spec.단위})` : spec.이름, control);
row.dataset.sheetVar = spec.이름;
return row;
}
/** 옵션 칸들 한 덩이(737-1) — 상자 폭 따라 6 → 4 → 3 → 2 → 1 열 격자(`calc_sheet.css`) · 없으면 빈 목록 */
export const optionGrid = (rows: HTMLElement[]): HTMLElement[] =>
rows.length
? [
el("div", {
className: "calc-sheet__opts",
children: [el("div", { className: "calc-sheet__opts-grid", children: rows })],
}),
]
: [];
/** 장비 · 하위 한 줄 — 노 · 재 · 경 칸 + 합 · 누르면(Enter · Space) 몫 식 줄 펼침 */
function mergedRows(m: SheetMerged): HTMLElement[] {
const share = (cost: string): number | null =>
m.몫.filter((x) => x.비목 === cost).reduce<number | null>((s, x) => add(s, x.줄.금액), null);
const short = t("Short")
.replace("{a}", won(share("노무비"), 1))
.replace("{b}", won(share("재료비"), 1))
.replace("{c}", won(share("경비"), 1));
const head = createCalcFormulaRow(`▸ ${m.이름}`, createCalcFormula(short), out(m.금액, false, 1));
// 펼침 = 부른 기계경비 로직 시간당 줄(손료 · 주연료 · 잡품 · 운전사 · 714-1b) → 이 줄 노 · 재 · 경 몫 식
const unfold = m.몫.find((x) => x.줄.펼침)?.줄.펼침;
const parts = [
...(unfold
? [
createText(
t("Per")
.replace("{n}", unfold.이름)
.replace("{v}", won(unfold.계, 1))
.replace("{u}", unfold.단위 || "-"),
"hint",
),
...unfold.줄.map((x) => lineRow(x, `${x.이름} (${x.비목})`)),
]
: []),
...m.몫.map((x) => lineRow(x.줄, `${x.비목}`)),
];
for (const p of parts) {
p.hidden = true;
p.dataset.sheetPart = m.이름;
}
head.dataset.sheetEquip = m.이름;
head.title = t("Fold");
head.tabIndex = 0;
head.setAttribute("role", "button");
head.setAttribute("aria-expanded", "false");
const label = head.firstElementChild as HTMLElement;
const toggle = (): void => {
const open = head.getAttribute("aria-expanded") !== "true";
head.setAttribute("aria-expanded", String(open));
label.textContent = `${open ? "▾" : "▸"} ${m.이름}`;
parts.forEach((p) => (p.hidden = !open));
};
head.addEventListener("click", toggle);
head.addEventListener("keydown", (ev) => {
if (ev.key !== "Enter" && ev.key !== " ") return;
ev.preventDefault();
toggle();
});
return [head, ...parts];
}
/** 묶음 머리 — 이름 · 기준 · 환산 근거 · 쓰는 입력 칸 */
function headRows(
name: string,
uses: string[],
o: SheetOptions,
specs: Map<string, SheetInput>,
basis: string[] = [],
notes: string[] = [],
): HTMLElement[] {
const vars = o.onInput ? uses.filter((n) => specs.has(n)) : [];
if (!vars.length && !o.bundle) return [];
const title = [name, basis.length ? `${t("Basis")} ${basis.join(" · ")}` : ""].filter(Boolean);
const head = createText(title.join(" — "), "muted", 14);
head.dataset.sheetHead = name;
const rows: HTMLElement[] = [head];
if (notes.length) rows.push(createText(notes.join(" · "), "hint"));
const onInput = o.onInput;
if (onInput)
for (const n of vars)
rows.push(varRow(specs.get(n)!, o.values?.[n] ?? "", (v) => onInput(n, v)));
return rows;
}
const subtotal = (g: SheetGroup): HTMLElement =>
createCalcFormulaRow(`${g.비목}${g.끝수 ? ` (${g.끝수})` : ""}`, null, out(g.소계, true));
/** 옵션으로 빠진 줄(753-1)은 안 그림 — 장비 · 하위 몫 줄은 몫이 다 빠졌을 때만 */
const shown = (l: SheetLine | SheetMerged): boolean =>
"몫" in l ? l.몫.some((x) => !x.줄.옵션꺼짐) : !l.옵션꺼짐;
/** 계산서 줄들 — 부르는 쪽 세로 칸(flex column)에 그대로 붙임 */
export function calcSheetRows(o: SheetOptions): HTMLElement[] {
const rows: HTMLElement[] = [];
const specs = new Map((o.inputs ?? []).map((s) => [s.이름, s]));
if (o.bundle) {
for (const b of bundleLines(o.묶음)) {
const name = bundleName(b.묶음);
rows.push(...headRows(name, b.쓰는입력, o, specs, b.기준단위, b.환산근거));
for (const line of b.줄.filter(shown))
rows.push(...("몫" in line ? mergedRows(line) : [lineRow(line)]));
rows.push(createCalcFormulaRow(name, null, out(b.소계, true, 1)));
}
rows.push(...o.묶음.map(subtotal)); // 맨 아래 비목 소계(서버 끝수 그대로) — 장비 노무는 여기서 인력 노무와 더해짐
} else {
for (const g of o.묶음) {
const uses = [...new Set(g.줄.flatMap((l) => l.쓰는입력 ?? []))];
rows.push(...headRows(g.비목, uses, o, specs));
rows.push(...g.줄.filter(shown).map((line) => lineRow(line)), subtotal(g));
}
}
rows.push(createCalcFormulaRow(o.sumLabel, null, out(o.계, true)));
return rows;
}
+1 -7
View File
@@ -142,18 +142,12 @@ export interface CalcRequest {
pick: (results: Record<string, unknown>) => unknown;
}
/** F7 구조물 면적 · C4 소단 구간 — 등록부 `node:` 입력 그대로. */
/** F7 구조물 면적 — 등록부 `node:` 입력 그대로. */
export const areasRequest = (rows: unknown[]): CalcRequest => ({
run: ["structure.layouts"],
inputs: { detail: { cross_sections: rows } },
pick: (results) => (results["structure.layouts"] as { areas: unknown }).areas,
});
export const bermRequest = (structures: unknown[]): CalcRequest => ({
run: ["cross.berm_spans"],
inputs: { berm: { structures, chainages: [] } },
pick: (results) => (results["cross.berm_spans"] as { spans: unknown }).spans,
});
export const trialRequest = (id: string, args: unknown[]): CalcRequest => ({
run: [id],
inputs: { args: { [id]: args } },
+1 -43
View File
@@ -8,20 +8,10 @@
* 입력 = 관 알림(배수유역도 · B06 관 목록) · 구조물(보기 고침 · 저장분) · 종류 표 · 측점 간격 · 추가 측점(가게)
* 관 id 장부 = 관 고정 id 가 쥔 세션 id 를 그 자리로(411-B) · 헛 id 정리(411-E) — 어느 보기에서 고쳐도
* 계획고 편집 옮김(사라진 측점 → 새 측점 · 213-2) = 사용자 고침만 · 노선 따라감(`follow`)은 안 옮김(계산 묶음 몫 · 349-0d)
* 소단 사본(세션 `berm`) = 구조물 소단 구간 — 쓰는 자리 여기 하나(B05 만 열어도 · 같으면 안 씀 · 쓰면 cross 한 번 · 467)
* ========================================================================== */
import { useDesignStore } from "./design_store";
import { pageRouteId } from "./design_page_route";
import { readState } from "../b_page_state";
import {
bermSpansFromStructures,
writeBermSpans,
type BermSpan,
} from "../../B06_Section/B06_Section_UI_Page_Persist";
import { pipeIdAt, placePipeIds, prunePipeIds, withPipeIds } from "./design_pipe_ids";
import { calcOnServerNow } from "./design_calc_sites";
import { bermRequest, calcChecked, calcOnServerOnly } from "./design_calc_server";
import { plannedIrregular } from "./design_structure_status";
import { planStations } from "../../common_util/common_util_structure_station";
import { followPipeReports } from "../../B05_Profile/logic/B05_Profile_Pipeline_Follow";
@@ -99,7 +89,7 @@ export function createFacilityLane(
let types: StructureType[] = [];
let stored: StructureInstance[] | null = null;
let structures: StructureInstance[] = [];
let structuresKnown = false; // 구조물을 받기 전 = 소단 사본 안 씀(빈 목록으로 저장분 소단을 지우지 않게)
let structuresKnown = false; // 구조물을 받기 전 = 측점 계획 모름(빈 목록으로 저장 시설 측점을 지우지 않게)
/** 마지막 관 알림 — 노선 따라감 · 관 초안이 바뀌면 이것을 초안 측점으로 다시 맞춤. */
let reported: LanePipe[] | null = null;
let pipes: LanePipe[] = [];
@@ -152,36 +142,6 @@ export function createFacilityLane(
const marks = (): StructureInstance[] => [...structures, ...pipesToStructureMarks(pipes)];
/** 소단 사본 = 지금 구조물의 소단 구간(467 · 사본 없는 동안 다시 셈 = 저장분 `design.berm`) · 같으면 안 씀 ·
* 쓰면 저장 노선 cross 한 번 더(같은 값 측점은 다시 셈 기억이 건너뜀 · 묶음은 늦게 불러옴 · 순환 없음). */
function syncBerm(): void {
const routeId = pageRouteId(projectId);
if (!structuresKnown || routeId === null) return;
// 계산 자리 quantity = server 면 잠잠 뒤 서버 값만(511-A) · 아니면 브라우저 셈
if (calcOnServerNow("quantity")) {
void calcOnServerOnly<BermSpan[]>("quantity", "C4.copy", bermRequest(structures)).then(
(next) => next && putBerm(routeId, next),
);
return;
}
putBerm(
routeId,
calcChecked("quantity", "cross.berm_spans", bermSpansFromStructures, [structures]),
);
}
function putBerm(routeId: number, next: BermSpan[]): void {
const copy = readState<BermSpan[]>("berm", projectId, routeId);
if (Array.isArray(copy) && JSON.stringify(copy) === JSON.stringify(next)) return;
writeBermSpans(projectId, routeId, next);
void Promise.all([import("./design_pipeline_cross"), import("./design_pipeline_page")])
.then(([cross, page]) => {
cross.forceCross();
page.pipelineCross(projectId);
})
.catch(() => undefined);
}
/** 측점 계획 입력이 다 옴 — 모름(관 알림 전 · 구조물 받기 전)을 빈 목록으로 셈하면 종단도가 저장 시설 측점
* (관 등)을 뺀 선으로 횡단을 다시 셈 · 유토 배지가 잠깐 틀림(473 · 요구 272). */
const known = (): boolean => types.length > 0 && reported !== null && structuresKnown;
@@ -234,14 +194,12 @@ export function createFacilityLane(
}
if (next.pipes) reported = [...next.pipes];
refresh(carryEdits);
if (next.structures) run(syncBerm);
},
follow() {
const drafted = store.get("structures");
structures = drafted ?? stored ?? [];
structuresKnown ||= drafted !== null || stored !== null;
refresh(false);
run(syncBerm);
},
setIntervalM(next) {
if (next > 0 && next !== intervalM) {
+13 -35
View File
@@ -4,7 +4,7 @@
* 공용 물음(`ui_template_leave_dialog` · [취소] [폐기] [저장]).
*
* 고침 = 초안 통 칸 중 고침이 아닌 칸(고름 · 관 id 장부 · 종류 캐시 · 고른 구조물 넘김 · 소단 사본 = 구조물 파생) ·
* 다른 페이지 칸(b08 · b09) · 저장 뒤에도 남기는 표준 횡단면(std-cross)을 뺀 것.
* 다른 페이지 칸(b08 · b09 · D · E) · 저장 뒤에도 남기는 표준 횡단면(std-cross)을 뺀 것.
* 횡단 초안은 [저장] 뒤에도 세션에 남음 — 그래서 「있음」이 아니라 **마지막 깨끗한 때와 다름**으로 봄:
* [저장] · [확정]이 끝나면 `markDesignClean` 이 그때 지문을 적음 · [초기화]는 초안을 다 지우고 새로 읽음.
* B05 ↔ B06 는 한 페이지 — 그 사이 이동은 묻지 않음.
@@ -12,18 +12,17 @@
* ========================================================================== */
import { ROUTES } from "@config/config_frontend";
import { leaveOrStay } from "@ui/ui_template_leave_dialog";
import { t as L } from "@ui/ui_template_locale";
import { listenWhileAlive } from "@ui/ui_template_window_watch";
import { guardProjectLeave } from "../b_project_switch";
import {
clearState,
draftFilled,
namesInBucket,
readByKey,
routeStateKeys,
stateKey,
type StateName,
} from "../b_page_state";
import { currentRoute, setLeaveGuard } from "../router";
/** 초안 통이지만 고침으로 안 보는 칸 — 묻지 않고 [폐기]에도 안 지움. */
const NOT_EDITS = new Set<StateName>([
@@ -32,17 +31,13 @@ const NOT_EDITS = new Set<StateName>([
"structure-pick",
"structure-type-cache",
"std-cross",
"berm", // 소단 사본 — 열 때 시설 줄이 구조물에서 씀(272 · 고침은 `structures` 칸)
]);
const OTHER_PAGE = /^b0[89]-/;
const OTHER_PAGE = /^(b0[89]|d0\d|e0\d)-/;
export function editDraftNames(): StateName[] {
return namesInBucket("draft").filter((name) => !NOT_EDITS.has(name) && !OTHER_PAGE.test(name));
}
const filled = (raw: string | null): raw is string =>
raw !== null && raw !== "" && raw !== "{}" && raw !== "[]";
/** 고침 칸 지문 — 모든 노선 것 · 빈 칸 뺌 · 고침이 없으면 "". */
export function editSignature(projectId: string): string {
const parts: string[] = [];
@@ -51,7 +46,7 @@ export function editSignature(projectId: string): string {
const keys = own ? [own] : routeStateKeys(name, projectId);
for (const key of keys.sort()) {
const raw = readByKey(key);
if (filled(raw)) parts.push(`${key}=${raw}`);
if (raw !== null && draftFilled(raw, name, projectId)) parts.push(`${key}=${raw}`);
}
}
return parts.join("\n");
@@ -93,10 +88,8 @@ export function discardDesignEdits(projectId: string): void {
editDraftNames().forEach((name) => clearState(name, projectId));
}
const SAME_PAGE = new Set<string>([ROUTES.B05_PROFILE, ROUTES.B06_SECTION]);
/**
* 페이지를 세울 때 한 번 — `root` 가 화면에서 빠지면 스스로 풂.
* 페이지를 세울 때 한 번 — `root` 가 화면에서 빠지면 스스로 풂(공용 `guardProjectLeave` 위 얇은 껍질).
* `save` = [저장] 한 줄기(`saveSharedDesign(shared, false)`).
*/
export function guardDesignLeave(
@@ -104,26 +97,11 @@ export function guardDesignLeave(
root: HTMLElement,
save: () => Promise<unknown>,
): void {
const unload = (event: BeforeUnloadEvent): void => {
if (!root.isConnected || !designDirty(projectId)) return;
event.preventDefault();
event.returnValue = "";
};
listenWhileAlive(window, "beforeunload", root, unload); // 다시 세운 옛 페이지를 안 붙듦(298-3)
const guard = async (): Promise<boolean> => {
if (!root.isConnected) return true;
if (SAME_PAGE.has(currentRoute())) {
// 라우터가 「넘어감」 뒤 물음을 내림 — 같은 페이지 안 이동이면 한 틱 뒤 다시 걺.
window.setTimeout(() => root.isConnected && setLeaveGuard(guard), 0);
return true;
}
if (!designDirty(projectId)) return true;
const got = await leaveOrStay(L("B05_Design_Leave"), {
save,
stillDirty: () => designDirty(projectId),
discard: () => discardDesignEdits(projectId),
});
return got !== "stay";
};
setLeaveGuard(guard);
guardProjectLeave(root, {
dirty: () => designDirty(projectId),
save,
discard: () => discardDesignEdits(projectId),
message: L("B05_Design_Leave"),
samePage: [ROUTES.B05_PROFILE, ROUTES.B06_SECTION],
});
}
+1 -8
View File
@@ -55,16 +55,9 @@
display: none;
}
.b05-route-profile__tools > .b05-route-profile__popout {
padding: var(--btn-bar-padding);
font-size: var(--btn-bar-font-size);
}
/* 유토곡선 · 배수유역 판 [창 분할](계획서 325-3) — 종단 [창 분할]과 같은 양식 · 유토는 판 좌상단에 띄움. */
/* 유토곡선 · 배수유역 판 [창 분할](계획서 325-3) — 종단 [창 분할]과 같은 양식(부품 단추 단 18 · 553-2) · 유토는 판 좌상단에 띄움. */
.b05-subpanel__popout {
flex: none;
padding: var(--btn-bar-padding);
font-size: var(--btn-bar-font-size);
}
.b05-profile__masshaul-overlay > .b05-subpanel__popout {
+9 -2
View File
@@ -15,7 +15,8 @@
* 숨으면 3D 그리기 루프 · 포인터 듣기를 떼고, 보이면 가게 값으로 다시 그린다.
* ========================================================================== */
import { CURRENT_PROJECT_ID_KEY, ROUTES, type RoutePath } from "@config/config_frontend";
import { ROUTES, type RoutePath } from "@config/config_frontend";
import { currentProjectId } from "../b_project_switch";
import { showToast } from "@ui/ui_template_elements";
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
import { createWorkflowLayout, type WorkflowLayoutHandle } from "@ui/ui_template_workflow_layout";
@@ -49,6 +50,12 @@ import {
peekSectionDetail,
} from "../../B06_Section/B06_Section_Section_Store";
import "./design_page.css";
// 클래스로 붙이는 부품(체크 · 빈 상태 · 범례 · 보조 글 · 표)의 CSS — B05 · B06 화면 파일이 시험 때문에 import 없이 클래스만 씀(553-2)
import "@ui/ui_template_checkbox";
import "@ui/ui_template_empty";
import "@ui/ui_template_flow_legend";
import "@ui/ui_template_table";
import "@ui/ui_template_text";
export type { DesignPageShared } from "./design_shared";
@@ -118,7 +125,7 @@ export function renderDesignPage(root: HTMLElement): MountedPage {
const page = document.createElement("div");
page.className = "design-page";
root.replaceChildren(page);
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY) ?? "";
const projectId = currentProjectId();
pinDraftProject(projectId); // 연 프로젝트 고정(388-3) — 다른 탭이 바꿔도 이 탭 초안 칸 그대로
void openCalcSites(projectId); // 계산 자리 판(504-2) — 열 때 한 번 · 열린 동안 바뀐 판은 다음 열기부터
usePipeIds(projectId || null); // 관 세션 id 장부 = 이 프로젝트(323-5 · 카드 · 알약 · 종단 · 3D 같은 id)
+10 -2
View File
@@ -15,6 +15,7 @@
import { fetchDetailPipePoints } from "../../B04_PreProcess/B04_PreProcess_Api_Fetch";
import type { SectionContextResponse } from "../../B06_Section/B06_Section_Api_Fetch";
import { forgetSavedLists, noteSavedList } from "../b_page_state";
import { setPageRouteId } from "./design_page_route";
export { pageRouteId } from "./design_page_route";
import {
@@ -36,11 +37,17 @@ function once<T>(key: string, load: () => Promise<T>): Promise<T> {
/** 저장 구조물(structures.json · 판번호 · 정의 판). */
export const pageStructures = (projectId: string): ReturnType<typeof fetchStructures> =>
once(`structures:${projectId}`, () => fetchStructures(projectId));
once(`structures:${projectId}`, () => fetchStructures(projectId)).then((saved) => {
noteSavedList(projectId, "structures", !!saved?.structures?.length); // 떠남 확인의 「[]」 판정(469-3)
return saved;
});
/** 저장 관(pipe_points.json · 유역). */
export const pagePipes = (projectId: string): ReturnType<typeof fetchDetailPipePoints> =>
once(`pipes:${projectId}`, () => fetchDetailPipePoints(projectId));
once(`pipes:${projectId}`, () => fetchDetailPipePoints(projectId)).then((saved) => {
noteSavedList(projectId, "pipes", !!saved?.pipe_points?.length);
return saved;
});
/** 정의 판이 바뀌었으면 종류 캐시 버림 — 페이지마다 한 번(탭이 다시 보일 때는 `onStaleStructureTypes`). */
export const pageTypesChecked = (projectId: string): Promise<boolean> =>
@@ -49,6 +56,7 @@ export const pageTypesChecked = (projectId: string): Promise<boolean> =>
/** 저장 정본이 바뀜 · 페이지를 떠남(`leaving` = 노선 id 칸도) — 다음에 부르면 한 번 다시 받음. */
export function forgetPageData(projectId: string, leaving = false): void {
for (const key of [...memo.keys()]) if (key.endsWith(`:${projectId}`)) memo.delete(key);
forgetSavedLists(projectId);
if (leaving) setPageRouteId(projectId, null);
}
+15 -5
View File
@@ -19,7 +19,7 @@
* 초안이 있으면 측점을 냄(새로고침 · 다시 열기 = 초안 측점 · 466).
* 노선 초안을 버리면(되돌리기 · [저장 전으로]) 초안 전 상세 · 횡단 초안 칸 그대로(349-0k · 349-0o) · 가게 측점 ·
* 종단 = 그 상세 측점 · 저장 기준(466) · 그 뒤 같은 측점 목록은 가게에 다시 안 냄(상세 측점이 묶음과 달라도 · 349-0q).
* [저장](sub5)이 서버 정본 — 노선을 먼저 보내고 `pendingAlignment(DRAFT_ROUTE_ID)` 를 새 노선에 보냄.
* [저장]이 서버 정본 — 노선 바뀐 [저장]의 계획고 = 저장분 옮김 대 손댐만(`routeGradeTouch` · 서버가 자기 옮김 위에 · 498-2).
* ========================================================================== */
import { createDesignPipeline, type DesignPipeline } from "./logic/design_pipeline";
@@ -91,6 +91,7 @@ import {
createPlanlineStage,
gradeRange,
routeRange,
type GradeTouch,
} from "../../B05_Profile/logic/B05_Profile_Pipeline_Grade";
import { groundPviAt, profileAt } from "./design_calc_profile";
import {
@@ -127,6 +128,7 @@ interface PagePipeline {
detail(): SectionDetailResponse | null;
/** 노선 초안 버림 동안(그리기 멈춤 ~ 풂) — 저장 노선 낡음 다시 셈을 안 띄움(472-5 덧). */
holding(): boolean;
touch(): Promise<GradeTouch | null>;
commit(): void;
dispose(): void;
}
@@ -137,6 +139,9 @@ type Facilities = Pick<DesignStoreValues, "pipes" | "structures">;
const pages = new Map<string, PagePipeline>();
const same = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);
/** 노선 바뀐 [저장]의 계획고 손댐(498-2) — 묶음이 없거나 못 가르면 null(본문 = 노선 0 칸 통째 · 옛 길). */
export const routeGradeTouch = async (id: string) => (await pages.get(id)?.touch()) ?? null;
/** 편집기에 넘길 다리 — 그 프로젝트 묶음을 처음 부를 때 세움. */
export function routeDraftBridge(
projectId: string,
@@ -271,7 +276,12 @@ function createPagePipeline(
route.curves,
(patch.pipes ?? inputs.pipes ?? []) as PlanPipe[],
(patch.structures ?? inputs.structures ?? []) as PlanStructure[],
{ intervalM: options.stationIntervalM, types: enabled, ...snapOptions(inputs.route, plan) },
{
intervalM: options.stationIntervalM,
types: enabled,
...snapOptions(inputs.route, plan),
plus: patch.plusStations ?? inputs.plusStations ?? undefined, // stations 단계와 같게(498-2)
},
).map((station) => station.chainageM),
});
/** cross 서버 단계 맥락(511-B B-4) — 저장 노선 · 상세 · 시설 · 편집(`design_pipeline_cross_server`). */
@@ -328,14 +338,13 @@ function createPagePipeline(
groundPvi: groundPviAt, // 계산 자리 「서버」 = 서버 Node(511-B B-3)
}),
profile: profileAt,
// 단면 유형 = 새 지형 상단측 · 소단 = 지금 구조물(365-5) · 저장 노선 = 다시 셈 창구 한 벌(365-4) · 엔진은
// 창구 출력을 앞 결과로 안 이어 씀(초안 = 새로고침)
// 단면 유형 = 상단측 · 소단 = 얹힌 제원(577-2) · 저장 노선 = 다시 셈 창구(365-4) · 엔진은 창구 출력 안 이어 씀
cross: (ctx) => {
const stations = ctx.out.ground?.stations ?? [];
const status = statusOf(ctx.inputs.pipes, ctx.inputs.structures);
const engine = () =>
crossStage.stage({
...contextCross(ctx, status, options.bermAt?.(ctx.inputs.structures) ?? null),
...contextCross(ctx, status),
prev: ctx.prev && savedOut.has(ctx.prev) ? null : ctx.prev,
});
// 계산 자리 cross = server — 엔진 길도 서버 Node 단계(같은 TS · 511-B B-4) · 못 받음 = 브라우저
@@ -678,6 +687,7 @@ function createPagePipeline(
ownsCross: Boolean(options.savedCross),
detail: () => draftDetailOf(projectId),
holding: () => holding,
touch: async () => (await pipe.settled(), followStage.touch(pageSaved.storedEdits(), pipe)),
commit: () => void (beforeDraft = null),
dispose() {
disposed = true;
@@ -82,6 +82,11 @@ export function createPageSaved(
savedSource,
canonChanged,
savedEdits,
/** 서버 저장분 계획고 편집(세션 초안 뺌) — 노선 바뀐 [저장]에서 서버가 옮기는 그 값(498-2). */
storedEdits(): AlignmentEdits {
const longitudinal = canonDetail()?.longitudinal;
return (longitudinal && readAlignment(longitudinal)?.edits) ?? emptyEdits();
},
storedPvi,
crossNow,
drafted,
@@ -14,7 +14,6 @@ import type {
AlignmentBase,
AlignmentEdits,
} from "../../B05_Profile/logic/B05_Profile_UI_Profile_Alignment";
import type { BermAt } from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
import type { SectionDetailResponse } from "../../B06_Section/B06_Section_Api_Fetch";
import type { DesignStoreValues } from "./design_store";
@@ -61,8 +60,6 @@ export interface PagePipelineOptions {
stationKeys?: (routeId: number, detail: SectionDetailResponse) => string[];
/** 고리가 `RECONCILE_ROUNDS` 바퀴 안에 안 맞음 — 「계산 불안정」 한 줄(365-2). */
onUnstable?: () => void;
/** 소단 구간(지금 구조물 목록 → 누가거리마다 제원) — 초안 cross 가 따라간 구조물로 소단을 셈(365-5). */
bermAt?: (structures: unknown) => BermAt;
/** 저장 노선 cross = 다시 셈 창구 한 벌(365-4 · 측점이 상세와 다르면 null = 엔진) · 주면 종단도는 따로 안 셈. */
savedCross?: (
d: SectionDetailResponse,
+10 -3
View File
@@ -24,6 +24,7 @@ import {
} from "../../B05_Profile/logic/B05_Profile_UI_Profile_Data";
import { showToast } from "@ui/ui_template_elements";
import { hasStaleDesigns } from "../../B06_Section/B06_Section_UI_Section_Common";
import { savedSectionDetail } from "../../B06_Section/B06_Section_Section_Canon";
import {
blocksMinCover,
findMinCoverViolations,
@@ -267,7 +268,8 @@ export function createDesignProfile(ctx: DesignProfileContext) {
restore: () => void (store.resync() ? rebuild() : draw()),
restoreSaved: () => store.restoreSaved(),
canRestoreSaved: () => store.canRestoreSaved(),
refresh: () => renderBalance(),
// 고름 직후 종단도도 한 번 — 붉은 강조가 다음 그림을 기다리지 않게(423).
refresh: () => (renderBalance(), drawSoon()),
});
/** 편집을 적용한다. 법정 위반 정책이 block이면 새 위반이 생기는 편집을 막는다. false = 막힘. */
@@ -294,9 +296,14 @@ export function createDesignProfile(ctx: DesignProfileContext) {
return true;
}
/** 계획선 측점 줄 = 측점 한 벌 — 셈은 `design_profile_base` 몫(계산 묶음 planline 도 같은 기준). */
/** 계획선 측점 줄 = 측점 한 벌 — 셈은 `design_profile_base` 몫(계산 묶음 planline 도 같은 기준). 옛 측점 =
* 저장 정본 상세 — 갈아끼운 상세(새 측점 목록)면 옮긴 구조물 옛 측점 꼭짓점이 안 옮겨져 계획고가 갈림(583). */
const listed = (): AlignmentBase | null =>
listedBase(storedBase, detail, planted ? irregularStations : null);
listedBase(
storedBase,
(routeId !== null && savedSectionDetail(projectId, routeId)) || detail,
planted ? irregularStations : null,
);
/** 계획고 [저장] 조각(472-6) — 저장 뒤 서버 선형으로 기준을 갈아 다시 그림. */
const alignmentSave = createProfileSave({
@@ -123,6 +123,7 @@ export function attachProfilePopout(
button: HTMLButtonElement,
): void {
button.className = "ui-btn ui-btn--ghost b05-route-profile__popout";
button.setAttribute("data-size", "18"); // 부품 단추 단 18(553-2) — 옆 도구 단추와 같은 키
button.title = "종단 하단 패널을 별도 창으로 분할 · 다시 누르면 결합";
attachPopout(profile, {
piece: profile.root,
@@ -139,6 +140,7 @@ export function attachSubPanelPopouts(profile: DesignProfile): void {
const subButton = (title: string): HTMLButtonElement => {
const button = document.createElement("button");
button.className = "ui-btn ui-btn--ghost b05-subpanel__popout";
button.setAttribute("data-size", "18");
button.title = title;
return button;
};
+32 -8
View File
@@ -11,7 +11,7 @@
* 노선(346-3 · 6-B) — 노선 초안이 마지막 저장과 다르면 같은 본문에 `route`(계약 `RouteDraft`) · `carry`(옛 노선
* 세션 횡단 고침 원본)를 실음. 서버가 맨 앞에서 노선 저장(관 · 구조물 · 추가 측점 좌표 따라감 · 종횡단 · 계획고
* 이월) → 관 · 구조물 초안 다시 얹기 → 새 노선으로 위 차례 → 횡단 = 세션 고침을 측점 열쇠로 넘긴 patch(같은 TS ·
* Node). 이 때 조각은 기본 조각만(화면 몫 일 = 옛 노선 판이라 건너뜀) · 계획고 초안 = 노선 초안 칸 ?? 옛 노선 칸 ·
* Node). 이 때 조각은 기본 조각만(화면 몫 일 = 옛 노선 판이라 건너뜀) · 계획고 = 손댐만(서버 옮김 위에 · 498-2) ·
* 손 상단측은 안 보냄(요구 274) · [확정]도 [저장]으로(새 노선을 본 뒤 다시 [확정]) · 다 저장하면 페이지 다시 세움.
*
* ⚠ 차례를 바꾸면 결과가 갈린다 — 관 목록은 스냅샷이라 구간값(암거 옵션)보다 먼저, 상단측은
@@ -49,6 +49,24 @@ import {
} from "../../B05_Profile/B05_Profile_UI_RouteEdit_Apply";
import type { RouteSaveBody } from "../../B05_Profile/B05_Profile_Api_Replan";
import { designSignature, markDesignClean } from "./design_leave";
import { routeGradeTouch } from "./design_pipeline_page";
import type { GradeTouch } from "../../B05_Profile/logic/B05_Profile_Pipeline_Grade";
/** [저장] · [확정] 막음(755-3) — 글을 돌려주면 알림하고 안 냄 · `root` 가 화면에서 빠지면 풂. */
const saveHolds: Array<{ root: HTMLElement; hold: () => string | null }> = [];
export function holdDesignSave(root: HTMLElement, hold: () => string | null): void {
saveHolds.push({ root, hold });
}
function heldSave(): string | null {
for (let index = saveHolds.length - 1; index >= 0; index -= 1) {
if (!saveHolds[index].root.isConnected) saveHolds.splice(index, 1);
}
for (const { hold } of saveHolds) {
const why = hold();
if (why) return why;
}
return null;
}
export interface DesignSaveContext {
projectId: string;
@@ -166,10 +184,12 @@ export function noticeDroppedEdits(saved: { dropped_edit_chainages?: number[] })
/** 노선 초안 동안의 계획고 초안 칸 — `design_pipeline_page` 의 `DRAFT_ROUTE_ID` 와 같은 값(sub7). */
const DRAFT_ROUTE_ID = 0;
/** 계획고 조각 — 노선이 바뀌면 노선 초안 칸(`DRAFT_ROUTE_ID`) ?? 옛 노선 칸의 초안(서버가 새 노선 번호로 고쳐
* 씀 · 측점이 없는 자리는 버리고 알림 · 213-2) · 저장 뒤 두 칸 다 비움. */
function alignmentPart(routeId: number, moved: boolean): SavePart | null {
const draft = (moved ? pendingAlignment(DRAFT_ROUTE_ID) : null) ?? pendingAlignment(routeId);
/** 계획고 조각 — 노선이 바뀌면 사용자 손댐(`touch` · 서버가 자기 옮김 위에 얹음 · 498-2) · 못 가르면 노선 초안
* 칸(`DRAFT_ROUTE_ID`) ?? 옛 노선 칸의 초안 통째(서버가 새 노선 번호로 고쳐 씀) · 측점이 없는 자리는 버리고 알림
* (213-2) · 저장 뒤 두 칸 다 비움. */
function alignmentPart(routeId: number, moved: boolean, touch: GradeTouch | null): SavePart | null {
const draft =
touch?.edits ?? (moved ? pendingAlignment(DRAFT_ROUTE_ID) : null) ?? pendingAlignment(routeId);
if (!draft) return null;
return {
body: { route_id: routeId, ...draft },
@@ -188,6 +208,7 @@ async function defaultPart(
routeId: number,
confirm: boolean,
moved: boolean,
touch: GradeTouch | null,
): Promise<SavePart | null> {
switch (key) {
case "settings": // 노선 뒤 — 노선 저장이 새로 만든 종단에도 경고 기준이 얹힘(448)
@@ -199,7 +220,7 @@ async function defaultPart(
case "plusStations": // 계획고 앞 — 추가 측점 자리의 계획고 편집이 버려지지 않게(368-2)
return plusStationsPart(projectId);
case "alignment":
return alignmentPart(routeId, moved);
return alignmentPart(routeId, moved, touch);
case "culvertOptions":
return culvertOptionsPart(stateKey("culvertopt", projectId, routeId));
case "uphill":
@@ -244,6 +265,8 @@ export async function saveDesign(
const confirm = options.confirm ?? false;
const routeId = ctx.routeId();
if (routeId === null) return false;
const held = heldSave();
if (held) return (showToast(held, "warning"), false);
saving.add(ctx.projectId);
let locked = confirm;
if (locked) showLoadingOverlay();
@@ -257,12 +280,13 @@ export async function saveDesign(
const parts: Partial<Record<SavePartKey, SavePart | null>> = {};
// 노선이 바뀌면 횡단 = 옛 노선 세션 고침 원본(서버가 새 측점에 넘김) · 화면 몫 조각은 옛 노선 판이라 안 씀
const carried = route ? routeCarryPart(ctx.projectId, routeId) : null;
const touch = route ? await routeGradeTouch(ctx.projectId) : null;
for (const key of SAVE_PART_KEYS) {
if (key === "cross") {
parts.cross = carried ? carried.cross : await ctx.crossPart(confirm);
continue;
}
const base = await defaultPart(key, ctx.projectId, routeId, confirm, !!route);
const base = await defaultPart(key, ctx.projectId, routeId, confirm, !!route, touch);
const own = route ? undefined : ctx.parts?.[key];
parts[key] = own ? await own(base, confirm) : base;
}
@@ -272,7 +296,7 @@ export async function saveDesign(
routeId,
confirm,
parts,
route ? { route, carry: carried?.carry } : {},
route ? { route, carry: carried?.carry, alignment_cleared: touch?.cleared } : {},
);
if (result.waited) showToast("앞 저장이 끝나기를 기다린 뒤 저장했습니다.", "info");
// 저장 도중 고침(가림막 동안 키 · 되돌리기 포함 · 505-2) — 조각 정리가 그 고침까지 비우므로 건너뜀(서버는 낸
+1 -1
View File
@@ -69,7 +69,7 @@ export async function postDesignSave(
routeId: number,
confirm: boolean,
parts: Partial<Record<SavePartKey, SavePart | null>>,
extra: { route?: unknown; carry?: unknown } = {},
extra: { route?: unknown; carry?: unknown; alignment_cleared?: unknown } = {},
): Promise<DesignSaveResult> {
const body: Record<string, unknown> = { route_id: routeId, confirm, ...extra };
for (const key of SAVE_PART_KEYS) {
+9 -14
View File
@@ -53,14 +53,14 @@ import {
holdSectionDetail,
invalidateSectionDetail,
restoreSectionDetail,
swapInFlight,
swapSectionStations,
} from "../../B06_Section/B06_Section_Section_Store";
import {
refreshCrossDesigns,
type CrossRefreshInput,
} from "../../B06_Section/B06_Section_Cross_Refresh";
import { bermSpansFromStructures, bermSpecAt } from "../../B06_Section/B06_Section_UI_Page_Persist";
import { areasRequest, bermRequest, calcChecked } from "./design_calc_server";
import { areasRequest, calcChecked } from "./design_calc_server";
import { registeredKeys, stationIntervalOf } from "../../B06_Section/B06_Section_Station_Keys";
import { detailCross } from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
import type { CrossOutput, PipelineStation } from "./logic/design_pipeline_types";
@@ -255,26 +255,21 @@ function startPagePipeline(shared: DesignPageShared, profile: DesignProfile): vo
each((hooks) => hooks.onFacilitiesFollowed?.(far));
},
onUnstable: () => showToast(L("Design_Pipeline_Unstable"), "warning"),
// 초안 소단 = 지금 구조물(따라간 자리)의 소단 구간(365-5 · 저장 노선은 다시 셈 창구가 세션 사본으로)
bermAt: (structures) => {
const list = Array.isArray(structures) ? structures : [];
// 계산 자리 quantity = server 면 잠잠 뒤 서버 대조만(초안 미리보기 · 511-A)
const spans = calcChecked("quantity", "cross.berm_spans", bermSpansFromStructures, [list], {
request: bermRequest(list),
});
return (chainageM) => bermSpecAt(spans, chainageM);
},
// 저장 노선 횡단 = 다시 셈 창구 한 벌(365-4) — 카드 · 유토곡선 · [저장] 패치가 보는 상세 설계를 늘 고치고 그
// 값을 묶음 출력으로(발자국 · 평면 횡단 판) · 측점이 상세와 다르면(갈아끼움 전) 묶음 엔진(439)
savedCross: (detail, routeId, stations) =>
refreshSavedCross(
// 새 측점(관 · 구조물 놓음) = 도는 측점 갈아끼움을 기다려 그 상세로 — 안 기다리면 엔진 길로 빠져 구조물
// 얹기(평면 비탈 끝 · 폐회로 면적) 없는 한 차례가 화면에 튐(583)
savedCross: async (saved, routeId, stations) => {
const detail = (await swapInFlight(projectId, routeId))?.detail ?? saved;
return refreshSavedCross(
{ projectId, routeId, detail, edits: profile.pipelineSource().edits },
stations,
(updated) => {
if (updated.length) crossDesignsUpdated(shared, updated);
profile.redraw();
},
),
);
},
plantedBase, // 저장 노선 종단 기준 = 입력이 심는 측점(386-3 · 보기 심기 무관)
// 저장 상세 측점마다 등록 열쇠 — 초안 자리로 뜬 시설 측점의 입력 짝(주인 id · 457)
stationKeys: (routeId, detail) =>
+5 -21
View File
@@ -23,7 +23,7 @@
import { onStateRemoved, readState, stateKey, writeState } from "../b_page_state";
import type { StateName } from "../b_page_state";
import { CURRENT_PROJECT_ID_KEY } from "../../config/config_frontend";
import { currentProjectId } from "../b_project_switch";
import type { StructureInstance } from "../../B05_Profile/B05_Profile_Api_Structures";
import type { DetailPipeInput } from "../../B04_PreProcess/B04_PreProcess_Api_Fetch";
import type { PageSettingsBody } from "../../B05_Profile/B05_Profile_Api_Fetch";
@@ -91,11 +91,7 @@ export function pinDraftProject(projectId: string | null): void {
export function draftProjectId(projectId: string | null = null): string {
if (projectId) return projectId;
if (openedProjectId) return openedProjectId;
try {
return window.localStorage.getItem(CURRENT_PROJECT_ID_KEY) || "-";
} catch {
return "-";
}
return currentProjectId() || "-";
}
interface Slot<T> {
@@ -177,14 +173,10 @@ type MemoryKey = keyof typeof MEMORY_SLOTS;
/** 멈춘 뒤 세션에 한 번 쓰기까지(ms). */
export const PERSIST_DELAY_MS = 1000;
const memory = new Map<string, unknown>();
const persistTimers = new Map<
string,
{ timer: ReturnType<typeof setTimeout>; write: () => void }
>();
const persistTimers = new Map<string, ReturnType<typeof setTimeout>>();
function cancelPersist(storageKey: string): void {
const pending = persistTimers.get(storageKey);
if (pending) clearTimeout(pending.timer);
clearTimeout(persistTimers.get(storageKey));
persistTimers.delete(storageKey);
}
@@ -193,14 +185,6 @@ onStateRemoved((storageKey) => {
cancelPersist(storageKey);
});
/** 미뤄 둔 세션 쓰기를 지금 — [저장] 앞 · 시험. */
export function flushDesignStore(): void {
const writes = [...persistTimers.values()];
persistTimers.forEach((pending) => clearTimeout(pending.timer));
persistTimers.clear();
writes.forEach((pending) => pending.write());
}
function memorySlot<T>(key: MemoryKey, projectId: string | null): Slot<T> {
const name = MEMORY_SLOTS[key];
const storageKey = name ? stateKey(name, projectId) : projectId && `memory:${key}:${projectId}`;
@@ -227,7 +211,7 @@ function memorySlot<T>(key: MemoryKey, projectId: string | null): Slot<T> {
persistTimers.delete(storageKey);
writeState(name, memory.get(storageKey) ?? null, projectId);
};
persistTimers.set(storageKey, { timer: setTimeout(write, PERSIST_DELAY_MS), write });
persistTimers.set(storageKey, setTimeout(write, PERSIST_DELAY_MS));
},
};
}
+4 -2
View File
@@ -136,6 +136,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
): HTMLElement => {
if (item.kind === "select") {
const select = document.createElement("select");
select.className = "ui-select";
const choices = choicesOf(item, values);
const now = values[item.id] ?? "";
const drawn = choices.find((ch) => ch.value === now);
@@ -154,6 +155,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
}
if (item.kind === "text") {
const input = document.createElement("input");
input.className = "ui-input";
input.type = "text";
input.maxLength = TEXT_MAX;
input.value = values[item.id] ?? "";
@@ -283,7 +285,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
if (!c.required && hooks.count?.(c) === undefined) {
const remove = document.createElement("button");
remove.type = "button";
remove.className = "b05-route__irregular-btn";
remove.className = "ui-btn ui-btn--danger-ghost b05-route__irregular-btn";
remove.textContent = "삭제";
remove.title = `${c.title} 상자 삭제`;
remove.dataset.specRemove = c.id;
@@ -333,7 +335,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
if (c.required || (c.max && list.length >= c.max) || hooks.count?.(c) !== undefined) continue;
const add = document.createElement("button");
add.type = "button";
add.className = "b05-route__irregular-btn b05-structure__spec-add";
add.className = "ui-btn ui-btn--ghost b05-route__irregular-btn b05-structure__spec-add";
add.textContent = `+ ${containerTitle(c, names, groupNames[c.link])}`;
add.title = "추가";
add.dataset.specAdd = c.id;
+1
View File
@@ -354,6 +354,7 @@ export async function mountCrossView(
// 폼 → 횡단 캐시 반영은 따로 뗀 모듈이 맡는다(2026-09-02 분리).
const pipeOptionsContext: PipeOptionsContext = {
detail: () => sectionDetail,
scope: () => `${projectId}:${currentRouteId}`,
ford: stationControls.ford,
box: stationControls.box,
revetOffset: stationControls.revetOffset,
-4
View File
@@ -11,7 +11,6 @@
* ========================================================================== */
import { showToast } from "@ui/ui_template_elements";
import { purgeOtherProjects } from "../b_asset_cache";
import { createProgressCircle } from "@ui/ui_template_progress";
import { fetchConfirmedSurface } from "../../B04_PreProcess/B04_PreProcess_Api_Fetch";
import { clearRouteLatestCache } from "../../B05_Profile/B05_Profile_Api_Fetch";
@@ -96,8 +95,6 @@ export async function mountProfileView(
): Promise<void> {
const activeProjectId = shared.projectId;
if (!activeProjectId) return;
// 새로고침으로 바로 들어온 경우에도 다른 프로젝트 자료는 보관함에서 지운다.
void purgeOtherProjects(activeProjectId);
const viewer = createRouteViewer();
// 구 우클릭 메뉴의 자유 텍스트 구조물 → 레지스트리 타입(2026-08-17 이관 매핑과 동일).
@@ -549,7 +546,6 @@ export async function mountProfileView(
contourIntervalM: latestResponse.surface_params.contour_interval_m,
smooth: latestResponse.surface_params.smooth,
tools: [view3d.modal.button],
recommend: panel.recommend,
// 계산 묶음(346-1) — 페이지가 세운 다리(365-1) · 노선을 고칠 때마다 종단이 따라옴(미리보기 · [저장]이 정본).
draft: routeDraftBridge(activeProjectId, profilePanel),
});
@@ -8,7 +8,7 @@
* 그리기 루프 · 포인터 듣기만 멈추고 WebGL 문맥 · 지형은 살린다. 버림은 페이지를 떠날 때만.
* ========================================================================== */
import { createButton } from "@ui/ui_template_elements";
import { mapToolButton } from "@ui/ui_template_map_tools";
import { openModal } from "@ui/ui_template_modal";
import { createProgressCircle } from "@ui/ui_template_progress";
@@ -73,9 +73,9 @@ export function createViewer3dModal(deps: Viewer3dModalDeps) {
shown.close();
}
const button = createButton({ label: "3D", variant: "ghost", onClick: open });
// 평면 도구줄 아이콘 단추(568-2) — 클래스는 가이드 촬영이 잡음.
const button = mapToolButton("view3d", { onClick: open }); // 마우스 글 = 공용 표(634-2)
button.classList.add("b05-route__3d-open");
button.title = "3D 화면을 큰 창으로 엽니다 — 닫으면 그리기를 멈춥니다";
return {
park,
@@ -18,7 +18,6 @@
* ========================================================================== */
import { readFileSync, statSync } from "node:fs";
import { bermSpansFromStructures, bermSpecAt } from "@util/common_util_berm_spans";
import type { FollowingStructure } from "@util/common_util_route_follow";
import { useCatalogDefinition, type CatalogDefinition } from "@util/common_util_structure_catalog";
import type { PlanPipe, PlanStructure, PlanType } from "@util/common_util_structure_station";
@@ -189,9 +188,7 @@ async function runStages(
ctx.inputs.structures ?? savedStructures,
types,
);
const list = Array.isArray(ctx.inputs.structures) ? ctx.inputs.structures : [];
const spans = bermSpansFromStructures(list);
return crossStage.stage(contextCross(ctx, status, (at) => bermSpecAt(spans, at)));
return crossStage.stage(contextCross(ctx, status));
},
};
if (stages.until === "follow") return followOnly(table, inputs);
@@ -10,7 +10,6 @@ import {
} from "../../../common_util/common_util_route_follow";
import {
crossWithContext,
type BermAt,
type CrossInput,
type ShapeStatus,
} from "../../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
@@ -45,11 +44,10 @@ export function placedFollowInputs(
};
}
/** cross 단계 입력 = 시설 맥락(새 자리 그림) · 단면 유형 기본값(상단측) · 소단(지금 구조물) 얹음(365-5 · 457). */
/** cross 단계 입력 = 시설 맥락(새 자리 그림 · 소단 = 얹힌 제원 · 577-2) · 단면 유형 기본값(상단측) 얹음(365-5 · 457). */
export const contextCross = (
ctx: StageContext<"cross">,
status: ShapeStatus | null,
bermAt: BermAt | null,
): StageContext<"cross"> => ({
...ctx,
inputs: {
@@ -58,7 +56,6 @@ export const contextCross = (
ctx.inputs.cross as CrossInput | null,
ctx.out.ground?.stations ?? [],
status,
bermAt,
),
},
});
@@ -76,6 +76,8 @@ export interface RouteDraft {
stationSnapStep?: number | null;
/** 분석 지형(`las` · `sheet` · null = 확정 지표면) — ground 단계가 고를 격자. */
terrain: string | null;
/** 고친 예정노선(626-3 · B05 평면 그리기) — 없음 · null = 그대로 · [] = 지움 · 점 묶음 = [저장] 때 `expected_route_edit.csv`. */
expected?: Vertex[] | null;
}
/** 묶음이 받는 입력 다섯. 없으면 null(= 저장분 그대로). cross · pipes · structures 모양은 주인 몫. */
+89
View File
@@ -0,0 +1,89 @@
/* =============================================================================
* formula_doc_copy.ts
* 수식 문서 사본 몫(PLAN 802-4b-2 · 요구 655-2 · 656 · 659-3) — M02 단가산출(SK) 수식 탭 머리
* 「M01 새 판 있음」 배지(값 줄 · 부름 문서가 지금 M01 과 다름) · [단가 갱신](자원 단가만 · 식 그대로) ·
* [재생성](지금 M01 문서로 다시 찍음 · 버리는 것 알림) — 누를 때만(자동 갱신 없음)
* 흐름 = 미리 셈(`{길}/preview` {할일}) → 확인 창(계 전 → 후 · 바뀐 자원 · 버리는 것) → 저장(`{길}/save` {할일, 판})
* 층 쓰기 권한 없으면 「읽기만」 배지
* ========================================================================== */
import { createBadge } from "@ui/ui_template_badge";
import { createButton } from "@ui/ui_template_button";
import { eqText } from "@ui/ui_template_equation";
import { showConfirmDialog } from "@ui/ui_template_elements";
import { ft } from "./formula_doc_text";
export type CopyJob = "단가갱신" | "재생성";
/** 사본 표지(서버 `M02_Doc_Copy.state` + 시점 · 따로) */
export interface CopyMark {
M01새판?: boolean;
지운키?: string[];
단가새값?: boolean;
문서새판?: string[];
따로?: boolean;
}
export interface CopyOptions {
/** SK 키 · 꼬리(`""` · `/preview` · `/table?key=…`) → 길(`/m02/layers/{층}/sangeun/{키}/doc…` · 주인 물음) */
route: (key: string, path: string) => string;
/** 저장 뒤 그 SK 새 판(페이지 [저장] 409 막음) */
onSaved?: (version: string) => void;
}
type Money = Record<string, number | null> | null;
export interface JobReply {
전?: Money;
후?: Money;
바뀐자원?: unknown[];
버림?: { 문서고침?: boolean; 끊음?: string[] };
}
/** 「M01 새 판 있음」 — 값 줄 · 단가 · 부름 문서 어느 하나라도 지금 M01 과 다름 */
export function isStale(m: CopyMark | undefined): boolean {
return !!m && (!!m.M01새판 || !!m.단가새값 || !!m.지운키?.length || !!m.문서새판?.length);
}
export function copyBadges(m: CopyMark | undefined, edit: boolean): HTMLElement[] {
const out: HTMLElement[] = [];
if (isStale(m)) {
const b = createBadge(ft("New_M01"), "danger");
b.dataset.copyStale = "";
out.push(b);
}
if (!edit) out.push(createBadge(ft("Read_Only"), "neutral"));
return out;
}
export function copyTools(edit: boolean, run: (job: CopyJob) => void): HTMLElement[] {
if (!edit) return [];
return (["단가갱신", "재생성"] as const).map((job) => {
const b = createButton({
label: job === "단가갱신" ? ft("Price") : ft("Regen"),
variant: "ghost",
size: 28,
onClick: () => run(job),
});
b.dataset.copyJob = job;
return b;
});
}
const sum = (m: Money | undefined): string =>
typeof m?.계 === "number" ? `${eqText(m.계)} 원` : "—";
/** 할일 알림 — 계 전 → 후 · 바뀐 자원 수 · 재생성이 버리는 것(이름) → 확인이면 true */
export function askJob(job: CopyJob, got: JobReply, names: Map<string, string>): Promise<boolean> {
const bits = [
job === "단가갱신" ? ft("Price_Ask") : ft("Regen_Ask"),
`${ft("Before_After")} ${sum(got.전)} → ${sum(got.후)}`,
];
if (job === "단가갱신") bits.push(`${ft("Changed")} ${got.바뀐자원?.length ?? 0}`);
const lost = [
...(got.버림?.문서고침 ? [ft("Regen_Edit")] : []),
...(got.버림?.끊음 ?? []).map((k) => `${ft("Cut")} ${names.get(k) || k}`),
];
if (job === "재생성" && lost.length) bits.push(`${ft("Regen_Lost")} — ${lost.join(" · ")}`);
return showConfirmDialog(bits.join(" · "), job === "단가갱신" ? ft("Price") : ft("Regen"));
}
+20
View File
@@ -0,0 +1,20 @@
/* =============================================================================
* formula_doc_foot.ts
* 수식 탭 맨 아래 합계 상자(PLAN 803-2a · 요구 667-2) — 단가산출 상세 탭과 같은 부품(`buildFoot` · 노무비 · 재료비 · 경비 · 계)
* 값 = 계산기 셈 그대로(여기서 다시 셈 안 함) · 셈 못 함 = 「—」
* ========================================================================== */
import type { DocAnswer } from "../common_util/common_util_formula_doc_calc";
import { buildFoot } from "../M01_MasterData/M01_MasterData_UI_LogicLab_Foot";
import "../M01_MasterData/M01_MasterData_UI_LogicLab_Style.css";
const num = (v: unknown): number => (typeof v === "number" && Number.isFinite(v) ? v : 0);
export function docFoot(a: DocAnswer): HTMLElement {
const done = !!a.셈됨 && typeof a.계 === "number";
return buildFoot(
{ labor: num(a.노무비), material: num(a.재료비), cost: num(a.경비) },
done,
num(a.계),
);
}
+55
View File
@@ -0,0 +1,55 @@
/* =============================================================================
* formula_doc_options.ts
* 수식 문서 옵션 가로(PLAN 802-3 · 802-4b-2 · 요구 655 「옵션 선택은 상단에 수평」) — 선택 · 입력 기호마다 칸 하나
* 값 = 화면 값(`given`) › 문서 값 · 고침은 부른 쪽 `set`(M01 = 화면 값만 · M02 사본 = 문서 값) · `lock` = 읽기만
* ========================================================================== */
import { createInputField, createSelectField } from "@ui/ui_template_input";
import { varId, type DocVar, type FormulaDoc } from "../common_util/common_util_formula_doc";
export function buildOptions(
doc: FormulaDoc | null,
given: Record<string, unknown>,
lock: boolean,
set: (key: string, v: DocVar, value: unknown) => void,
): HTMLElement[] {
const out: HTMLElement[] = [];
for (const v of doc?.변수 ?? []) {
const kind = v.연결?.갈래;
const k = v.열쇠;
if (!k || (kind !== "선택" && kind !== "입력")) continue;
const label = v.이름 || varId(v);
const now = k in given ? given[k] : v.연결?.값;
if (kind === "선택") {
const list = (v.연결?.목록 ?? []).map(String);
const f = createSelectField({
label,
options: list.map((x) => ({ value: x, text: x })),
value: String(now ?? ""),
compact: true,
size: 28,
disabled: lock,
onChange: (x) => set(k, v, x),
});
f.root.dataset.option = k;
out.push(f.root);
} else {
const f = createInputField({
label,
value: now === undefined || now === null ? "" : String(now),
compact: true,
size: 28,
unit: v.단위 || undefined,
});
f.input.disabled = lock;
f.input.addEventListener("change", () => {
const t = f.input.value.trim();
const n = Number(t.replace(/,/g, ""));
set(k, v, t !== "" && Number.isFinite(n) ? n : t);
});
f.root.dataset.option = k;
out.push(f.root);
}
}
return out;
}
+82
View File
@@ -0,0 +1,82 @@
/* =============================================================================
* formula_doc_render.ts
* 수식 문서 닿는 기호(PLAN 802-3 · 804-6) — 줄 쌓기 산출근거(`line_stack_out.ts`)가 씀
* reach = 지금 탄 갈래로 닿는 기호(만약 안 탄 갈래 · 숨김 자리 인자 빼고)
* 옛 수식 문서 탭의 조판 그림(renderNode · 값 식 · 수 바꿈)은 804-6 에 걷음
* ========================================================================== */
import {
linkNames,
varId,
type FormulaDoc,
type Node,
} from "../common_util/common_util_formula_doc";
import {
offLine,
takenOf,
tryParse,
type Values,
} from "../common_util/common_util_formula_doc_view";
/** 탄 갈래로 부르는 이름(만약 안 탄 갈래 · 숨김 자리 인자 빼고) */
function namesTaken(n: Node, vals: Values, out: string[] = []): string[] {
const add = (x: string): void => {
if (!out.includes(x)) out.push(x);
};
switch (n[0]) {
case "var":
add(n[1]);
break;
case "bin":
namesTaken(n[2], vals, out);
namesTaken(n[3], vals, out);
break;
case "neg":
case "pct":
case "field":
namesTaken(n[1], vals, out);
break;
case "call": {
const pick = takenOf(n, vals);
const items = pick ? [n[2][0], n[2][pick]] : n[2];
items.forEach((x) => namesTaken(x, vals, out));
break;
}
}
return out;
}
const RESERVED_MONEY = new Set(["노무비", "재료비", "경비", "번지", "단계"]);
/** 정의 영역에 보일 기호 — 식 줄 · 금액 줄 · 결과에서 지금 탄 갈래로 닿는 것(보이는 차례) */
export function reach(doc: FormulaDoc, vals: Values, showOff = false): string[] {
const vars = new Map((doc.변수 ?? []).map((v) => [varId(v), v]));
const lines = new Map((doc.식 ?? []).map((l) => [String(l.기호 ?? ""), l.식]));
const money = new Set((doc.금액 ?? []).map((l) => String(l.기호 ?? "")).filter(Boolean));
const out: string[] = [];
const visit = (name: string): void => {
if (!name || out.includes(name) || RESERVED_MONEY.has(name) || money.has(name)) return;
out.push(name);
if (lines.has(name)) {
const n = tryParse(lines.get(name));
if (n) namesTaken(n, vals).forEach(visit);
}
const v = vars.get(name);
if (v?.연결 && v.연결.갈래 !== "식") linkNames(v.연결).forEach(visit);
};
const from = (text: unknown): void => {
const n = tryParse(text);
if (n) namesTaken(n, vals).forEach(visit);
};
for (const l of doc.식 ?? []) {
visit(String(l.기호 ?? ""));
from(l.식);
}
for (const l of doc.금액 ?? []) {
if (!showOff && offLine(l, vals, lines)) continue;
from(l.수량);
from(l.단가);
}
if (doc.결과) visit(String(doc.결과));
return out;
}
+96
View File
@@ -0,0 +1,96 @@
/* =============================================================================
* formula_doc_text.ts
* 수식 문서 화면 글(PLAN 802-3 · 803-2a) — [국문, 영문]
* ========================================================================== */
import { currentLanguageIndex } from "@ui/ui_template_locale";
const TEXT = {
Sym_Head: ["기호 식", "Symbol equation"],
Val_Head: ["값 식", "Value equation"],
Defs_Head: ["기호 정의", "Symbols"],
New_Group: ["새 묶음", "New group"],
Unnamed: ["이름 없음", "Unnamed"],
Subtotal: ["소계", "Subtotal"],
Sum: ["계", "Sum"],
Total: ["합계", "Total"],
Need: ["정의 필요", "Needs definition"],
Unused: ["쓰지 않는 기호", "Unused symbols"],
Show_Off: ["꺼진 줄 보기", "Show off lines"],
Hide_Off: ["꺼진 줄 숨김", "Hide off lines"],
Save: ["수식 저장", "Save equation"],
Discard: ["고침 버림", "Discard"],
Saved: ["수식 저장됨", "Equation saved"],
Save_Failed: ["저장 못 함", "Save failed"],
Load_Failed: ["수식 문서를 못 읽음", "Failed to load"],
Leave: ["저장 안 한 수식 고침이 있음 — 버리고 나갈까요?", "Discard unsaved edits?"],
Leave_Ok: ["버리고 나감", "Discard"],
Result: ["결과", "Result"],
Result_Sym: ["결과 기호", "Result symbol"],
Dirty: ["저장 안 한 고침", "Unsaved edits"],
Sign: ["기호", "Symbol"],
Low: ["아래첨자", "Subscript"],
Up: ["위첨자", "Superscript"],
Name: ["국문 이름", "Name"],
Unit: ["단위", "Unit"],
Link: ["연결", "Link"],
Drop_Var: ["정의 지움", "Delete definition"],
Value: ["값", "Value"],
List: ["목록(· 로 나눔)", "Options (· separated)"],
Default: ["기본", "Default"],
Low_Bound: ["아래 끝", "Min"],
High_Bound: ["위 끝", "Max"],
Find: ["찾기", "Search"],
Column: ["칸", "Column"],
Cond: ["조건", "Condition"],
Inputs: ["넘길 입력", "Inputs"],
Region: ["지역 기호", "Region symbol"],
Make_Line: ["식 줄 만들기", "Add line"],
By_Line: ["식 줄로 정의", "Defined by a line"],
Pick_First: ["찾아 고름", "Search and pick"],
Material: ["재료 고르기", "Material pick"],
None: ["없음", "None"],
Open_Sub: ["식 펼침", "Show equation"],
Bad_Sign: ["기호 꼴 아님 · 같은 기호 있음", "Bad or duplicate symbol"],
More: ["더 있음 — 찾는 말을 더 넣으세요", "More — refine search"],
Nothing: ["찾은 것 없음", "Nothing found"],
Linked: ["연결", "Linked"],
Cut: ["끊음", "Unlinked"],
Unlink: ["연결 끊기", "Unlink"],
Relink: ["다시 잇기", "Relink"],
Ask_Relink: [
"이 기초에서 고친 것을 버리고 부르던 문서로 돌아감",
"Discard edits to this basis and follow the copy again",
],
New_M01: ["M01 새 판 있음", "New M01 version"],
Price: ["단가 갱신", "Refresh prices"],
Regen: ["재생성", "Regenerate"],
Price_Ask: ["자원 단가만 지금 M01 값으로 — 식 · 연결 그대로", "Prices only — equation unchanged"],
Regen_Ask: ["지금 M01 수식 문서로 다시 찍음", "Re-copy from the current M01 equation"],
Regen_Lost: ["버리는 것", "Discarded"],
Regen_Edit: ["수식 고침", "Equation edits"],
Changed: ["바뀐 자원", "Changed resources"],
Before_After: ["계", "Sum"],
Job_Done: ["적용함", "Applied"],
Dirty_First: [
"저장 안 한 고침이 있음 — [저장] 또는 [되돌림] 먼저",
"Save or discard edits first",
],
No_Key: [
"저장한 단가산출만 수식 문서를 엶 — [단가산출 상세] 탭",
"Saved items only — use the detail tab",
],
Read_Only: ["읽기만", "Read only"],
Tools: ["수식 도구", "Equation tools"],
Tool_Frac: ["분수", "Fraction"],
Tool_Sub: ["아래첨자", "Subscript"],
Tool_Sup: ["위첨자", "Superscript"],
Tool_Paren: ["( )", "( )"],
Tool_If: ["만약", "If"],
Tool_Round: ["반올림", "Round"],
Tool_Group: ["묶음", "Group"],
} as const;
export type TextKey = keyof typeof TEXT;
export const ft = (key: TextKey): string => TEXT[key][currentLanguageIndex === 1 ? 1 : 0];
+24 -9
View File
@@ -3,11 +3,12 @@
* D 예정노선 · E 안정성평가 단계 묶음(요구 345 · PLAN 529-1) — B05~B09 틀(`createWorkflowLayout`)을
* 단계 목록 · 아이콘 · 주소 인자로 그대로 씀. 빈 단계 = 틀 · 좌측 패널([?] [<] [>]) · 단계 막대만.
* 페이지 길 = 각 폴더 `draft/<폴더>_Route.ts` · 본문이 생기면 그 폴더 페이지로 갈아 끼움.
* 떠남(560-4) = `beforeLeave` 가 true 일 때만 — 단계 아이콘 · [<] [>] 는 기다린 뒤 이동 ·
* 머리 메뉴 · 뒤로 가기는 같은 함수를 `setLeaveGuard` 로(false = 머묾).
* ========================================================================== */
import {
API_BASE_URL,
CURRENT_PROJECT_ID_KEY,
PLAN_STEP_ROUTES,
STAB_STEP_ROUTES,
type RoutePath,
@@ -15,7 +16,8 @@ import {
import { el, hideLoadingOverlay, showLoadingOverlay, showToast } from "@ui/ui_template_elements";
import { t, type LocaleKey } from "@ui/ui_template_locale";
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
import { navigateTo, type PageRenderer } from "./router";
import { guardProjectLeave, setCurrentProject, type ProjectLeaveGuard } from "./b_project_switch";
import { navigateTo, setLeaveGuard, type PageRenderer } from "./router";
interface GroupFlow {
routes: readonly RoutePath[];
@@ -26,8 +28,8 @@ interface GroupFlow {
const FLOWS: readonly GroupFlow[] = [
{
routes: PLAN_STEP_ROUTES,
labels: ["DE_Step_PlanBase", "DE_Step_PlanAnalysis", "DE_Step_PlanConfirm"],
icons: ["📍", "🧭", "✅"],
labels: ["DE_Step_PlanAnalysis", "DE_Step_PlanConfirm"],
icons: ["🧭", "✅"],
},
{
routes: STAB_STEP_ROUTES,
@@ -35,11 +37,11 @@ const FLOWS: readonly GroupFlow[] = [
"DE_Step_StabGround",
"DE_Step_StabSection",
"DE_Step_Stab3D",
"DE_Step_StabReport",
"DE_Step_StabRock",
"DE_Step_StabArea",
"DE_Step_StabReport",
],
icons: ["🪨", "📐", "🧊", "📄", "⛰️", "🗺️"],
icons: ["🪨", "📐", "🧊", "⛰️", "🗺️", "📄"],
},
];
@@ -55,11 +57,21 @@ export function groupFlowOf(
export function mountGroupStep(
root: HTMLElement,
route: RoutePath,
parts: { mainContent?: HTMLElement; leftPanel?: HTMLElement } = {},
parts: {
mainContent?: HTMLElement;
leftPanel?: HTMLElement;
/** 옛 떠남(저장 등 · false = 머묾) — 페이지가 dirty · save 로 옮기면 지움 */
beforeLeave?: () => Promise<boolean>;
} & Partial<ProjectLeaveGuard> = {},
): void {
const flow = FLOWS.find((f) => f.routes.includes(route));
if (!flow) throw new Error(`단계 묶음 밖 주소: ${route}`);
const active = flow.routes.indexOf(route);
const leave = parts.beforeLeave;
if (leave) setLeaveGuard(async () => !root.isConnected || (await leave()));
// 떠남 셋(401) — 단계 아이콘 · [<] [>] · 머리 메뉴 · 뒤로 모두 라우터 물음 한 자리([취소 · 폐기 · 저장])
const { dirty, save, discard, message } = parts;
if (dirty && save) guardProjectLeave(root, { dirty, save, discard, message });
const layout = createWorkflowLayout({
title: t(flow.labels[active]),
steps: flow.labels.map((key) => t(key)),
@@ -69,7 +81,10 @@ export function mountGroupStep(
leftPanel: parts.leftPanel,
mainContent:
parts.mainContent ?? el("p", { className: "app-placeholder", text: t("DE_Step_Empty") }),
onStepClick: (_index, next) => navigateTo(next as RoutePath),
onStepClick: (_index, next) => {
if (!leave) return navigateTo(next as RoutePath);
void leave().then((ok) => ok && navigateTo(next as RoutePath));
},
});
root.append(layout.root);
}
@@ -102,7 +117,7 @@ export async function createGroupProject(
const detail = typeof data.detail === "string" ? data.detail : `HTTP ${response.status}`;
throw new Error(detail);
}
localStorage.setItem(CURRENT_PROJECT_ID_KEY, data.project_id);
setCurrentProject(data.project_id);
showToast(t("DE_Reg_Success"), "success");
navigateTo(firstStep);
} catch (error) {
+56
View File
@@ -4,9 +4,65 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>임도 설계 및 견적 자동화</title>
<link rel="icon" href="data:," />
<!-- 고른 테마를 첫 그림 전에(앱 initThemeAndLang 과 같은 열쇠) · 고른 게 없으면 불러오는 중 판만
시스템 설정(어두움)을 따름 -->
<script>
try {
var t = localStorage.getItem("frd_theme");
if (t === "dark" || t === "light") document.documentElement.setAttribute("data-theme", t);
if (t === "dark" || (!t && matchMedia("(prefers-color-scheme: dark)").matches))
document.documentElement.setAttribute("data-boot-dark", "");
} catch (e) {}
</script>
<!-- 불러오는 중(555-1) — 테마 css 가 오기 전이라 색을 박음(--color-bg · --color-text-muted 값) ·
첫 페이지가 #app-outlet 에 그려지면 숨김 · 20 s 뒤 느린 연결 안내 · 도는 그림 없음 -->
<style>
#boot-loader {
position: fixed;
inset: 0;
z-index: 2147483000;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
background: #f6f7f9;
color: #6b7280;
font:
15px/1.5 "Noto Sans KR",
"Malgun Gothic",
"Apple SD Gothic Neo",
sans-serif;
text-align: center;
padding: 16px;
}
[data-boot-dark] #boot-loader {
color-scheme: dark;
background: #121417;
color: #9aa1ab;
}
#boot-loader-slow {
visibility: hidden;
font-size: 13px;
animation: boot-loader-slow 0s 20s forwards;
}
@keyframes boot-loader-slow {
to {
visibility: visible;
}
}
body:has(#app-outlet > *) #boot-loader {
display: none;
}
</style>
</head>
<body>
<div id="app"></div>
<div id="boot-loader" role="status">
<div>불러오는 중…</div>
<div id="boot-loader-slow">연결이 느림 — 조금 더 기다리거나 새로고침</div>
</div>
<script type="module" src="./main.ts"></script>
</body>
</html>
+68 -3
View File
@@ -6,13 +6,20 @@
* - 없음 · 깨짐 · Node(저장소 없음) = 기본값(= 지금 동작) · 의존 없음 · 불러올 때 window 안 만짐(CAD 도 import)
* - 휠 확대 방향 하나로 모든 화면 일체 — 기본 밂 = 휠을 앞으로(deltaY < 0) 굴리면 확대(오토캐드) · 당김 = 몸 쪽으로
* - 핀치(트랙패드 · 손가락)는 설정과 상관없이 늘 벌리면 확대(deltaY < 0)
* - 화면 옮기기 단추 하나(PLAN 588-4) — 고른 단추로 끌면 옮김 · 안 끌고 떼면 그 단추 제 몫 · 가운데는 늘 옮김
* - 바꾼 키(`keys`)는 기본과 다른 것만 — 판정 · 등록부는 `key_registry.ts`
* ========================================================================== */
export type WheelZoom = "pull" | "push";
export type PanButton = "left" | "middle" | "right";
export interface InputPrefs {
/** 휠 확대 방향 — pull = 당기면 확대 · push = 밀면 확대 */
wheelZoom: WheelZoom;
/** 화면 옮기기 단추 */
panButton: PanButton;
/** 바꾼 단축키 `{동작 id: [키]}` — 기본과 다른 것만(`key_registry.ts`) */
keys: Record<string, string[]>;
/** CAD 내 별칭 `{명령: [별칭]}`(대문자 · acad.pgp 식 — 명령 이름 > 내 별칭 > 기본 별칭) */
cadAliases: Record<string, string[]>;
}
@@ -20,7 +27,25 @@ export interface InputPrefs {
/** `stateKey("input-prefs")` 와 같은 키(시험이 맞춰 봄) — 등록표를 불러오면 window 가 딸려 와서 글자로 둠 */
export const INPUT_PREFS_KEY = "aislo:pref:input-prefs";
export const DEFAULT_INPUT_PREFS: InputPrefs = { wheelZoom: "push", cadAliases: {} };
export const DEFAULT_INPUT_PREFS: InputPrefs = {
wheelZoom: "push",
panButton: "middle",
keys: {},
cadAliases: {},
};
const PAN_BUTTONS: PanButton[] = ["left", "middle", "right"];
const defaults = (): InputPrefs => ({ ...DEFAULT_INPUT_PREFS, keys: {}, cadAliases: {} });
const cleanKeys = (raw: unknown): Record<string, string[]> => {
const out: Record<string, string[]> = {};
if (!raw || typeof raw !== "object") return out;
for (const [id, list] of Object.entries(raw as Record<string, unknown>)) {
if (Array.isArray(list)) out[id] = list.filter((k): k is string => typeof k === "string");
}
return out;
};
const cleanAliases = (raw: unknown): Record<string, string[]> => {
const out: Record<string, string[]> = {};
@@ -39,17 +64,57 @@ const cleanAliases = (raw: unknown): Record<string, string[]> => {
export function inputPrefs(): InputPrefs {
try {
const raw = globalThis.localStorage?.getItem(INPUT_PREFS_KEY);
if (!raw) return { ...DEFAULT_INPUT_PREFS, cadAliases: {} };
if (!raw) return defaults();
const value = JSON.parse(raw) as Partial<InputPrefs> | null;
return {
wheelZoom: value?.wheelZoom === "pull" ? "pull" : "push",
panButton: PAN_BUTTONS.includes(value?.panButton as PanButton)
? (value!.panButton as PanButton)
: "middle",
keys: cleanKeys(value?.keys),
cadAliases: cleanAliases(value?.cadAliases),
};
} catch {
return { ...DEFAULT_INPUT_PREFS, cadAliases: {} };
return defaults();
}
}
/** 고른 화면 옮기기 단추 */
export const panButton = (): PanButton => inputPrefs().panButton;
/** 이 단추(`MouseEvent.button` 0 왼 · 1 가운데 · 2 오른)로 끌면 화면 옮김 — 고른 단추 · 가운데는 늘 */
export const isPanButton = (button: number): boolean =>
button === 1 || button === PAN_BUTTONS.indexOf(panButton());
/** 왼쪽 아닌 옮기기 버튼(가운데 · 오른쪽을 고른 때) — 왼쪽이 고르기 · 돌림 몫인 화면용 */
export const isAuxPanButton = (button: number): boolean => button !== 0 && isPanButton(button);
/** 단추 번호 → `buttons` 비트(왼 1 · 오른 2 · 가운데 4) — 끄는 중 놓침 알아채기 */
export const buttonMask = (button: number): number => [1, 4, 2][button] ?? 0;
/** 이만큼(px) 끌어야 옮김 — 덜 움직이고 떼면 그 단추 제 몫(고름 · 메뉴 · Enter) */
export const PAN_MOVE_PX = 4;
/** 오른쪽 끌기로 옮긴 뒤 놓을 때 뜨는 메뉴를 한 번 막음 */
export function swallowNextMenu(): void {
const stop = (event: Event): void => {
event.preventDefault();
event.stopImmediatePropagation();
};
globalThis.addEventListener("contextmenu", stop, { capture: true, once: true });
setTimeout(() => globalThis.removeEventListener("contextmenu", stop, true), 400);
}
/** 오른쪽 누름으로 옮기기를 시작할 때 부름 — 끌고 놓으면 그 뒤 메뉴를 막음 */
export function guardMenuAfterDrag(down: PointerEvent): void {
if (down.button !== 2) return;
const up = (event: PointerEvent): void => {
const moved = Math.hypot(event.clientX - down.clientX, event.clientY - down.clientY);
if (moved > PAN_MOVE_PX) swallowNextMenu();
};
globalThis.addEventListener("pointerup", up, { capture: true, once: true });
}
/** 휠 한 번이 확대인가 — 설정 방향을 따름 */
export const wheelIn = (deltaY: number): boolean =>
inputPrefs().wheelZoom === "pull" ? deltaY > 0 : deltaY < 0;
+347
View File
@@ -0,0 +1,347 @@
/* =============================================================================
* key_registry.ts
* 단축키 등록부(PLAN 588-2 · 3) — 화면마다 실제로 받는 키 · 마우스 동작 한 벌 · ☰ [설정] 표가 여기서 그려짐.
*
* - 줄 = 동작 id · 자리 · 기본 키 · 이름 · `fixed`(못 바꿈 — Esc · 방향키 · 마우스 · 글 입력 · 시트 키)
* - 판정 = `keyIs(evt, id)` — 사용자가 바꾼 키(`input-prefs` 의 `keys`) 있으면 그것만 · 없으면 기본 키
* - 키 글 = 「Ctrl+Shift+Z」 꼴 — 글자 · 숫자는 자판 자리(`keyLetter` · 한글 자판 안전) · 차례 Ctrl · Alt · Shift · 키
* - 겹침 = 같은 화면에서 함께 듣는 자리끼리 · 「모든 화면」과 · 브라우저 몫 · CAD 글자 단독(명령행 몫)
* - 불러올 때 window 안 만짐(CAD 도 import) · 시험 = `resources/tester/test_588_key_registry.py`
* ========================================================================== */
import { inputPrefs } from "./input_prefs";
import { keyLetter } from "./key_shortcut";
export type KeyScope =
| "all"
| "b05"
| "profile"
| "routeEdit"
| "drainage"
| "section"
| "plan"
| "b04"
| "cad"
| "b08"
| "b09"
| "m02"
| "stab"
| "etc";
type Text = readonly [ko: string, en: string];
export interface KeyAction {
id: string;
scope: KeyScope;
/** 기본 키(키 글) · 못 바꾸는 줄은 보일 글(마우스 · 「글자」 등) */
keys: readonly string[];
label: Text;
fixed?: true;
}
/** 자리 이름 · 함께 듣는 화면(같은 화면이면 키가 겹치면 안 됨) — 표 차례도 이대로 */
export const KEY_SCOPES: Record<KeyScope, { label: Text; page: string }> = {
all: { label: ["모든 화면", "All views"], page: "all" },
b05: { label: ["B05 · B06 공통", "B05 · B06 common"], page: "b05" },
profile: { label: ["B05 종단", "B05 profile"], page: "b05" },
routeEdit: { label: ["B05 노선 편집(평면)", "B05 route edit (plan)"], page: "b05" },
drainage: { label: ["B05 배수 · 3D", "B05 drainage · 3D"], page: "b05" },
section: { label: ["B06 횡단", "B06 cross section"], page: "b05" },
plan: { label: ["D03 예정노선", "D03 planned route"], page: "plan" },
b04: { label: ["B04 지도 · 3D", "B04 map · 3D"], page: "b04" },
cad: { label: ["CAD", "CAD"], page: "cad" },
b08: { label: ["B08 수량 · 이정표", "B08 quantity · milestones"], page: "b08" },
b09: { label: ["B09 내역", "B09 estimate"], page: "b09" },
m02: { label: ["M02 양식", "M02 templates"], page: "m02" },
stab: { label: ["E03 · E04 · E07 안정성", "E03 · E04 · E07 stability"], page: "stab" },
etc: { label: ["그 밖", "Other"], page: "etc" },
};
const ARROWS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"] as const;
const SHIFT_ARROWS = ARROWS.map((key) => `Shift+${key}`);
/** 화면 옮기기 — ☰ [설정] 마우스 탭에서 고른 버튼(가운데는 늘) */
const PAN = "옮기기 버튼 끌기";
const A = (
id: string,
scope: KeyScope,
keys: readonly string[],
label: Text,
fixed?: true,
): KeyAction => (fixed ? { id, scope, keys, label, fixed } : { id, scope, keys, label });
const F = true as const;
/* prettier-ignore */
export const KEY_ACTIONS: readonly KeyAction[] = [
A("all.escape", "all", ["Escape"], ["☰ · 창 · 메뉴 닫기 · 고름 풀기", "☰ · close window · menu · clear selection"], F),
A("all.loupe", "all", ["Z"], ["누른 채 = 돋보기", "Hold = loupe"], F),
A("all.loupeRight", "all", ["오른쪽 0.4초 누름"], ["돋보기", "Loupe"], F),
A("all.loupeWheel", "all", ["돋보기 중 휠"], ["돋보기 배율", "Loupe zoom"], F),
A("all.zoomPan", "all", [PAN], ["그림 칸 화면 옮김(횡단 · 단면 · 미리 보기)", "Pan drawings (sections · previews)"], F),
A("all.zoomReset", "all", ["두 번 누름"], ["그림 칸 배율 처음대로", "Reset drawing zoom"], F),
A("all.resizer", "all", ["칸 경계 두 번 누름"], ["칸 크기 처음대로", "Reset panel size"], F),
A("all.grip", "all", ["ArrowLeft", "ArrowRight", "Shift+ArrowLeft", "Shift+ArrowRight"], ["칸 경계 손잡이에서 크기 바꿈", "Resize at a panel grip"], F),
A("all.touchLong", "all", ["손가락 길게 누름"], ["오른쪽 누름(메뉴)", "Right click (menu)"], F),
A("all.touchDouble", "all", ["손가락 두 번 톡"], ["두 번 누름", "Double click"], F),
A("all.touchPinch", "all", ["두 손가락 벌림"], ["확대", "Zoom in"], F),
A("b05.undo", "b05", ["Ctrl+Z"], ["되돌림(평면 · 종단 · 횡단 · 관 · 구조물)", "Undo (plan · profile · section · pipes · structures)"]),
A("b05.redo", "b05", ["Ctrl+Shift+Z", "Ctrl+Y"], ["다시", "Redo"]),
A("b05.escape", "b05", ["Escape"], ["모든 보기의 고름 풀기", "Clear selection in every view"], F),
A("profile.grade", "profile", ["ArrowUp", "ArrowDown"], ["계획고 0.1 m · [쉬프트] 중 구간 평행 옮김 · 누른 채 = 되풀이", "Grade 0.1 m · parallel shift · hold = repeat"], F),
A("profile.station", "profile", ["ArrowLeft", "ArrowRight"], ["구조물 · 부정 측점 옮김(종단을 마지막에 누른 때)", "Move structure · irregular station"], F),
A("profile.freeFinish", "profile", ["Enter"], ["자유지정 끝", "Finish free mode"]),
A("profile.freeBack", "profile", ["Backspace"], ["자유지정 마지막 점 빼기", "Free mode: remove last point"]),
A("profile.escape", "profile", ["Escape"], ["자유지정 취소 · 직선화 시작 측점 취소", "Cancel free mode · straighten start"], F),
A("profile.wheel", "profile", ["휠"], ["가로 스크롤(그래프 · 유토곡선 · 측점 표)", "Horizontal scroll"], F),
A("profile.drag", "profile", ["왼쪽 끌기", PAN], ["가로 옮김", "Horizontal pan"], F),
A("profile.menu", "profile", ["오른쪽 누름"], ["구조물 메뉴(넣기 · 지움)", "Structure menu"], F),
A("profile.pill", "profile", ["알약 끌기", "알약 오른쪽 누름"], ["구조물 옮김 · 지움", "Move · delete structure"], F),
A("profile.marker", "profile", ["측점 표식 끌기"], ["측점 옮김", "Move station"], F),
A("profile.markerPick", "profile", ["Enter", "Space"], ["초점 측점 표식 고름", "Pick focused station marker"], F),
A("profile.balloon", "profile", ["풍선 두 번 누름"], ["유토곡선 풍선 자동 자리", "Balloon back to auto"], F),
A("profile.hold", "profile", ["▲▼ 누른 채"], ["되풀이(0.5초 뒤 초당 10번)", "Repeat while held"], F),
A("routeEdit.nudge", "routeEdit", ARROWS, ["교각점 0.1 m(측점 맞춤이면 한 칸) · 고른 마커 옮김", "Move IP 0.1 m · picked marker"], F),
A("routeEdit.nudgeBig", "routeEdit", SHIFT_ARROWS, ["교각점 1 m 옮김", "Move IP 1 m"], F),
A("routeEdit.unmark", "routeEdit", ["Escape"], ["마커 고름 풀기", "Clear marker pick"], F),
A("routeEdit.zoom", "routeEdit", ["휠"], ["커서 자리 확대 · 축소", "Zoom at cursor"], F),
A("routeEdit.rotate", "routeEdit", ["Ctrl+휠"], ["지도 5° 돌림", "Rotate map 5°"], F),
A("routeEdit.pan", "routeEdit", [PAN], ["화면 옮김", "Pan"], F),
A("routeEdit.measure", "routeEdit", ["Shift+누름"], ["재기", "Measure"], F),
A("routeEdit.drag", "routeEdit", ["노드 · 손잡이 끌기"], ["교각점 옮김 · 반지름 바꿈", "Move IP · change radius"], F),
A("routeEdit.tick", "routeEdit", ["측점 눈금 누름"], ["그 측점 횡단 미리 보기", "Preview that station's section"], F),
A("routeEdit.splice", "routeEdit", ["선 두 번 누름"], ["노드 넣기", "Add node"], F),
A("routeEdit.unsplice", "routeEdit", ["노드 오른쪽 누름"], ["노드 지움", "Delete node"], F),
A("routeEdit.menu", "routeEdit", ["노선 오른쪽 누름"], ["구조물 메뉴 · 마커 지움", "Structure menu · delete marker"], F),
A("routeEdit.contour", "routeEdit", ["등고선 누름"], ["등고선 고정 단추", "Contour lock button"], F),
A("drainage.zoom", "drainage", ["휠"], ["배수유역도 확대 · 축소", "Drainage map zoom"], F),
A("drainage.pan", "drainage", [PAN], ["배수유역도 옮김", "Drainage map pan"], F),
A("drainage.pipe", "drainage", ["관 표식 끌기"], ["관 옮김", "Move pipe"], F),
A("drainage.basin", "drainage", ["누름"], ["유역 고름 · 풂", "Pick · unpick basin"], F),
A("drainage.menu", "drainage", ["오른쪽 누름"], ["배수 · 3D 노선 메뉴", "Drainage · 3D route menu"], F),
A("drainage.orbit", "drainage", ["3D 왼쪽 끌기"], ["누른 점 둘레로 돌림", "Orbit around picked point"], F),
A("drainage.pan3d", "drainage", [`3D ${PAN}`], ["3D 화면 옮김", "3D pan"], F),
A("drainage.zoom3d", "drainage", ["3D 휠"], ["3D 커서 자리 확대", "3D zoom at cursor"], F),
A("section.pad", "section", ARROWS, ["열린 조정 판 ▲▼◀▶", "Open adjust pad ▲▼◀▶"], F),
A("section.zoom", "section", ["Ctrl+휠"], ["카드 확대(휠만 = 카드 목록 스크롤)", "Card zoom (wheel alone scrolls)"], F),
A("section.detailEnter", "section", ["Enter"], ["상세 창 고르기 칸에 넣음", "Detail: put the pick in the cell"], F),
A("section.detailEscape", "section", ["Escape"], ["상세 창 고르기 취소", "Detail: cancel picking"], F),
A("section.detailAdd", "section", ["Shift+누름", "Ctrl+누름"], ["상세 창 선 조각 · 면 더함", "Detail: add line pieces · faces"], F),
A("section.detailMenu", "section", ["표 칸 오른쪽 누름"], ["상세 표 칸 메뉴", "Detail table cell menu"], F),
A("section.hold", "section", ["▲▼ 누른 채"], ["선 칸 되풀이", "Line cell repeat"], F),
A("plan.undo", "plan", ["Ctrl+Z"], ["되돌림", "Undo"]),
A("plan.redo", "plan", ["Ctrl+Shift+Z", "Ctrl+Y"], ["다시", "Redo"]),
A("plan.drawFinish", "plan", ["Enter"], ["그리기 끝", "Finish drawing"]),
A("plan.drawBack", "plan", ["Backspace"], ["그리기 마지막 점 빼기", "Drawing: remove last point"]),
A("plan.drawKind", "plan", ["Tab"], ["긋는 중 방식 자유지정 ↔ 길이·기울기 고정", "Drawing: free ↔ length + slope"]),
A("plan.drawBetween", "plan", ["Shift 누른 채"], ["길이·기울기 고정 긋는 중 부채꼴 사이 기울기 자리(길이 그대로)", "Drawing: pick between-slope on fan (length kept)"], F),
A("plan.delete", "plan", ["Delete"], ["고른 노드 · 선 · 구역 · 전체 · 마커 지움", "Delete picked node · segment · region · all · marker"]),
A("plan.escape", "plan", ["Escape"], ["그리기 취소 · 고름 풀기 · 재기 끝", "Cancel drawing · clear selection · end measure"], F),
A("plan.zoom", "plan", ["휠"], ["커서 자리 확대 · 축소", "Zoom at cursor"], F),
A("plan.rotate", "plan", ["Ctrl+휠"], ["지도 돌림", "Rotate map"], F),
A("plan.pan", "plan", [PAN], ["화면 옮김", "Pan"], F),
A("plan.select", "plan", ["누름 · 다시 누름"], ["노드 → 선 → 구역 → 전체 고름", "Pick node → segment → region → all"], F),
A("plan.vertex", "plan", ["고른 선 끌기", "노드 끌기"], ["꼭짓점 넣고 끌기 · 노드 옮김(지점 같이)", "Insert · drag vertex (point follows)"], F),
A("plan.unvertex", "plan", ["노드 · 마커 오른쪽 누름"], ["메뉴 [노드 지움] · [마커 지움]", "Menu: delete node · marker"], F),
A("plan.measure", "plan", ["재기: 누름 · 누름"], ["시점 · 종점", "Start · end"], F),
A("plan.menu", "plan", ["오른쪽 누름"], ["지도 메뉴 — 긋기 · 지점 놓기 · 노드 지움 · 연도 구역 나눔", "Map menu — draw · place point · delete node · split year"], F),
A("plan.point", "plan", ["지점 핀 끌기"], ["지점 옮김(그 자리 노드 같이 · 노드 12 px 안 = 붙음)", "Move point (with its node · snaps within 12 px)"], F),
A("b04.zoom", "b04", ["휠"], ["지도 · 3D 확대 · 축소", "Map · 3D zoom"], F),
A("b04.pan", "b04", [PAN], ["지도 · 3D 옮김", "Map · 3D pan"], F),
A("b04.pick", "b04", ["누름"], ["유역 · 흐름 점 고름", "Pick basin · flow point"], F),
A("b04.pipe", "b04", ["관 표식 끌기"], ["관 옮김", "Move pipe"], F),
A("b04.menu", "b04", ["오른쪽 누름"], ["노선 · 관 메뉴", "Route · pipe menu"], F),
A("b04.orbit", "b04", ["3D 왼쪽 끌기"], ["누른 점 둘레로 돌림", "Orbit around picked point"], F),
A("cad.snap", "cad", ["F3"], ["스냅", "Snap"]),
A("cad.grid", "cad", ["F7"], ["그리드", "Grid"]),
A("cad.ortho", "cad", ["F8"], ["직교", "Ortho"]),
A("cad.polar45", "cad", ["F10"], ["45°", "45°"]),
A("cad.undo", "cad", ["Ctrl+Z"], ["되돌림", "Undo"]),
A("cad.redo", "cad", ["Ctrl+Y", "Ctrl+Shift+Z"], ["다시", "Redo"]),
A("cad.save", "cad", ["Ctrl+S"], ["저장(M02)", "Save (M02)"]),
A("cad.selectAll", "cad", ["Ctrl+A"], ["모두 고름", "Select all"]),
A("cad.delete", "cad", ["Delete"], ["고른 것 지움", "Delete selection"]),
A("cad.copy", "cad", ["Ctrl+C"], ["복사", "Copy"]),
A("cad.copyBase", "cad", ["Ctrl+Shift+C"], ["기준점 복사", "Copy with base point"]),
A("cad.cut", "cad", ["Ctrl+X"], ["잘라 냄", "Cut"]),
A("cad.pasteBlock", "cad", ["Ctrl+Shift+V"], ["블록으로 붙여넣기", "Paste as block"]),
A("cad.properties", "cad", ["Ctrl+1"], ["특성", "Properties"]),
A("cad.paste", "cad", ["Ctrl+V"], ["붙여넣기", "Paste"], F),
A("cad.enter", "cad", ["Enter", "Space", "오른쪽 누름"], ["입력 끝 · 쉴 때 명령 반복", "Submit · repeat command when idle"], F),
A("cad.escape", "cad", ["Escape"], ["명령 취소 · 쉴 때 고름 풀기", "Cancel command · clear selection when idle"], F),
A("cad.type", "cad", ["글자"], ["명령 · 값 입력", "Type command · value"], F),
A("cad.backspace", "cad", ["Backspace"], ["입력 글 지움", "Erase typed text"], F),
A("cad.tab", "cad", ["Tab"], ["명령 후보 돌림 · 동적 입력 칸 넘김", "Cycle candidates · next dynamic field"], F),
A("cad.recall", "cad", ["ArrowUp", "ArrowDown"], ["기록 · 최근 값 · 후보", "History · recent values · candidates"], F),
A("cad.arrowPan", "cad", ["Shift+ArrowUp", "Shift+ArrowDown", "ArrowLeft", "ArrowRight"], ["화면 옮김", "Pan"], F),
A("cad.zoom", "cad", ["휠"], ["확대 · 축소", "Zoom"], F),
A("cad.pan", "cad", [PAN], ["화면 옮김", "Pan"], F),
A("cad.snapMenu", "cad", ["Shift+오른쪽 누름"], ["점 물음에서 스냅 메뉴", "Snap menu at a point prompt"], F),
A("cad.extents", "cad", ["가운데 두 번 누름"], ["범위 줌", "Zoom extents"], F),
A("cad.box", "cad", ["왼쪽 끌기"], ["고르기 상자(오른쪽으로 = 안 · 왼쪽으로 = 걸침)", "Selection window · crossing"], F),
A("b08.zoom", "b08", ["휠"], ["이정표 그림 확대 · 축소", "Milestone diagram zoom"], F),
A("b08.pan", "b08", [PAN, "빈 곳 왼쪽 끌기"], ["이정표 그림 옮김", "Milestone diagram pan"], F),
A("b08.reset", "b08", ["빈 곳 두 번 누름"], ["배율 처음대로", "Reset zoom"], F),
A("b08.leg", "b08", ["구간 글 두 번 누름", "Enter", "Escape"], ["노선 종류 · km 고침 · 적용 · 취소", "Edit road type · km · apply · cancel"], F),
A("b08.box", "b08", ["상자 · 잇는 점 끌기"], ["옮김 · 이음", "Move · link"], F),
A("b08.rowMenu", "b08", ["이정표 표 줄 오른쪽 누름"], ["이음 메뉴", "Link menu"], F),
A("b08.cell", "b08", ["두 번 누름", "Enter", "F2"], ["표 칸 입력 · Enter = 넣음", "Edit table cell · Enter = commit"], F),
A("b09.indent", "b09", ["Tab", "Shift+Tab"], ["내역 줄 들임 · 내어씀", "Indent · outdent row"], F),
A("b09.level", "b09", ["ArrowLeft", "ArrowRight"], ["내역 줄 단 바꿈", "Change row level"], F),
A("b09.move", "b09", ["ArrowUp", "ArrowDown", "Alt+ArrowUp", "Alt+ArrowDown"], ["내역 줄 위 · 아래로", "Move row up · down"], F),
A("b09.edit", "b09", ["Enter", "F2", "두 번 누름"], ["고른 줄 · 칸 입력", "Edit picked row · cell"], F),
A("b09.escape", "b09", ["Escape"], ["입력 끝 · 고름 풀기 · 빨강 표시 지움 · 뜬 창 닫기", "End edit · unpick · clear highlight · close popups"], F),
A("b09.menu", "b09", ["칸 · 줄 오른쪽 누름"], ["칸 · 줄 메뉴", "Cell · row menu"], F),
A("b09.drag", "b09", ["줄 끌기"], ["세로 = 자리 · 가로 = 단", "Vertical = position · horizontal = level"], F),
A("m02.save", "m02", ["Ctrl+S"], ["카드 저장", "Save card"], F),
A("m02.treeMove", "m02", ARROWS, ["나무: ↑↓ 초점 · →← 펼침 · 접음", "Tree: ↑↓ focus · →← expand · collapse"], F),
A("m02.rename", "m02", ["F2"], ["나무 이름 바꿈", "Tree rename"], F),
A("m02.delete", "m02", ["Delete"], ["나무 · 흐름도 고른 것 지움", "Delete selected (tree · data flow)"], F),
A("m02.copy", "m02", ["Ctrl+C", "Ctrl+V"], ["나무 복사 · 붙여넣기", "Tree copy · paste"], F),
A("m02.multi", "m02", ["Ctrl+누름", "Shift+누름"], ["나무 여럿 고름", "Tree multi-select"], F),
A("m02.menu", "m02", ["나무 빈 곳 오른쪽 누름"], ["맨 위 메뉴", "Top menu"], F),
A("m02.zoom", "m02", ["휠", "Ctrl+휠"], ["흐름도 · 노임 그림 배율", "Data flow · labor diagram zoom"], F),
A("m02.pan", "m02", [PAN, "Space+끌기"], ["흐름도 · 노임 그림 옮김", "Data flow · labor diagram pan"], F),
A("m02.boxMove", "m02", ARROWS, ["흐름도 고른 상자 10 px(Shift 50)", "Move selected box 10 px (Shift 50)"], F),
A("m02.undo", "m02", ["Ctrl+Z"], ["흐름도 되돌림", "Data flow undo"], F),
A("m02.escape", "m02", ["Escape"], ["잇기 · 알약 취소 · 크게 보기 줄임 · 열린 창 닫기", "Cancel link · pill · shrink full view · close window"], F),
A("m02.sheet", "m02", ["시트 안"], ["엑셀 키 그대로", "Excel keys"], F),
A("stab.zoom", "stab", ["휠"], ["지도 · 그림 확대 · 축소", "Map · drawing zoom"], F),
A("stab.pan", "stab", ["왼쪽 끌기", PAN], ["E04 · E07 지도 · E03 그림 옮김", "Pan"], F),
A("stab.fit", "stab", ["두 번 누름"], ["전체 보기", "Fit"], F),
A("etc.chat", "etc", ["Enter", "Shift+Enter"], ["B01 지식 물음 보냄 · 줄 바꿈", "B01 chat send · new line"], F),
A("etc.help", "etc", ["두 번 누름", "Ctrl+Enter", "Escape"], ["도움말 고침(관리자) · 저장 · 취소", "Help edit (admin) · save · cancel"], F),
A("etc.cadTab", "etc", ["CAD 탭 두 번 누름"], ["탭 이름 바꿈", "Rename tab"], F),
A("etc.sheet", "etc", ["항목 고르기 표"], ["방향키 · Tab · Enter · F2 · Delete · 글자 = 입력", "Arrows · Tab · Enter · F2 · Delete · typing"], F),
A("etc.toolbar", "etc", ["지도 도구줄 휠"], ["넘친 도구줄 가로 스크롤", "Scroll overflowing toolbar"], F),
];
const BY_ID = new Map(KEY_ACTIONS.map((action) => [action.id, action]));
/** 브라우저 몫 — 못 씀 */
export const RESERVED_KEYS = [
"F5",
"F11",
"F12",
"Ctrl+W",
"Ctrl+T",
"Ctrl+N",
"Ctrl+R",
"Ctrl+Shift+T",
];
const MODIFIERS = new Set(["Control", "Shift", "Alt", "Meta", "AltGraph", "CapsLock", "Process"]);
const NAMED: Record<string, string> = {
" ": "Space",
Spacebar: "Space",
Esc: "Escape",
Del: "Delete",
};
/** 누른 키 → 키 글 · 수정키만 · 한글 조합 = 빈 글 */
export function chordOf(
evt: Pick<KeyboardEvent, "key" | "code" | "ctrlKey" | "altKey" | "shiftKey"> &
Partial<Pick<KeyboardEvent, "metaKey">>,
): string {
const letter = keyLetter(evt);
const raw = NAMED[evt.key] ?? evt.key;
if (!letter && (!raw || MODIFIERS.has(raw))) return "";
const key = letter ? letter.toUpperCase() : raw.length === 1 ? raw.toUpperCase() : raw;
const parts = [
evt.ctrlKey || evt.metaKey ? "Ctrl" : "",
evt.altKey ? "Alt" : "",
evt.shiftKey ? "Shift" : "",
];
return [...parts.filter(Boolean), key].join("+");
}
/** 지금 쓰는 키 — 바꾼 것(`keys`) 있으면 그것 · 없으면 기본 */
export function actionKeys(
id: string,
keys: Record<string, string[]> = inputPrefs().keys,
): readonly string[] {
const action = BY_ID.get(id);
if (!action) return [];
return (!action.fixed && keys[id]) || action.keys;
}
/** 이 키가 그 동작인가 */
export const keyIs = (
evt: Parameters<typeof chordOf>[0],
id: string,
keys?: Record<string, string[]>,
): boolean => {
const chord = chordOf(evt);
return !!chord && actionKeys(id, keys).includes(chord);
};
/** 그 자리 바꿀 수 있는 동작 중 이 키에 맞는 첫 동작 id */
export function keyAction(evt: Parameters<typeof chordOf>[0], scope: KeyScope): string | null {
const chord = chordOf(evt);
if (!chord) return null;
const keys = inputPrefs().keys;
return (
KEY_ACTIONS.find((a) => a.scope === scope && !a.fixed && actionKeys(a.id, keys).includes(chord))
?.id ?? null
);
}
export type KeyError = "dup" | "reserved" | "letter";
/** 함께 듣는 자리인가 — 같은 화면 · 「모든 화면」 */
const together = (a: KeyScope, b: KeyScope): boolean =>
a === "all" || b === "all" || KEY_SCOPES[a].page === KEY_SCOPES[b].page;
/** 바꾼 키 검사 — `{동작 id: 첫 잘못}` · 비면 통과 · 겹침은 바꾼 줄에 붙임(상대가 못 바꾸는 줄이어도) */
export function checkKeys(keys: Record<string, string[]>): Record<string, KeyError> {
const errors: Record<string, KeyError> = {};
const live = KEY_ACTIONS.map((a) => ({ a, list: actionKeys(a.id, keys) }));
for (const { a, list } of live) {
if (a.fixed || !keys[a.id]) continue;
for (const key of list) {
const error: KeyError | null = RESERVED_KEYS.includes(key)
? "reserved"
: a.scope === "cad" && /^(Shift\+)?.$/.test(key)
? "letter"
: live.some(
({ a: b, list: other }) =>
b !== a && together(a.scope, b.scope) && other.includes(key),
)
? "dup"
: null;
if (error) {
errors[a.id] ??= error;
break;
}
}
}
return errors;
}
const LABEL: Record<string, string> = {
ArrowUp: "↑",
ArrowDown: "↓",
ArrowLeft: "←",
ArrowRight: "→",
Escape: "Esc",
};
/** 키 글 → 보일 글(↑ · Esc) */
export const keyLabel = (chord: string): string =>
chord
.split("+")
.map((part) => LABEL[part] ?? part)
.join("+");
+397
View File
@@ -0,0 +1,397 @@
/* 줄 쌓기 탭(804-2) — 위 옵션 · 가운데 왼쪽 편집 | 오른쪽 산출근거 · 맨 아래 합계 상자 · 색은 공통 값만 */
.lstk {
display: flex;
flex-direction: column;
gap: var(--spacing-12);
min-width: 0;
container-type: inline-size;
}
.lstk__head {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
}
.lstk__bar,
.lstk__badges,
.lstk__tools {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--spacing-8);
}
.lstk__areas {
display: grid;
grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
gap: var(--spacing-12);
align-items: start;
}
.lstk:not([data-edit="1"]) .lstk__areas {
grid-template-columns: minmax(0, 1fr);
}
.lstk:not([data-edit="1"]) .lstk__left {
display: none;
}
/* 합계 상자(요구 687-3) — 스크롤해도 화면 아래 고정 */
.lstk__foot {
position: sticky;
inset-block-end: 0;
z-index: 2;
padding-block: var(--spacing-8);
background: var(--color-surface);
border-block-start: 1px solid var(--color-border);
}
.lstk__foot:empty {
display: none;
}
/* 산출근거(요구 683) — 왼쪽을 내려도 화면에 고정 · 길면 자기 스크롤(높이 = 스크롤 영역 − 합계 상자 · fit()) */
.lstk[data-edit="1"] .lstk__right {
position: sticky;
inset-block-start: 0;
max-height: var(--lstk-view, 100vh);
overflow: auto;
}
/* 탭 폭이 좁으면(1366 화면 등) 편집 위 · 산출근거 아래(고정 없음) */
@container (max-width: 1040px) {
.lstk__areas {
grid-template-columns: minmax(0, 1fr);
}
.lstk[data-edit="1"] .lstk__right {
position: static;
max-height: none;
overflow: visible;
}
}
/* ── 왼쪽 편집 ── */
.lstk-edit {
display: flex;
flex-direction: column;
gap: var(--spacing-16);
}
/* 항목(804-5) — 머리 띠(바탕 · 왼쪽 굵은 띠 · 번호) · 줄 = 들여쓰기 + 왼쪽 세로 선 · 가나다 = 한 단 더 들여씀 */
.lstk-item {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
}
.lstk-item.is-sub {
margin-inline-start: var(--spacing-24);
}
.lstk-item + .lstk-item.is-sub {
margin-block-start: calc(-1 * var(--spacing-8));
}
/* 그룹 머리 한 줄(요구 687-6 · 688) — 이름 칸이 늘고 줄어 [+] · X 는 늘 오른쪽 끝(접어도 그대로) */
.lstk-ihead {
flex-wrap: nowrap;
align-items: center;
padding: var(--spacing-4) var(--spacing-8);
border-inline-start: 4px solid var(--color-accent);
border-radius: var(--radius-sm);
background: var(--color-surface-sunken);
}
.lstk-item.is-sub > .lstk-ihead {
border-inline-start-color: var(--color-border-strong);
}
.lstk-ihead > .lstk-f-l {
flex: 1 1 auto;
min-width: 4em;
}
.lstk-count {
flex: none;
white-space: nowrap;
}
.lstk-no {
flex: none;
min-width: 1.5em;
font-weight: var(--font-weight-bold);
color: var(--color-text);
}
.lstk-lines {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
margin-inline-start: var(--spacing-12);
padding-inline-start: var(--spacing-12);
border-inline-start: 2px solid var(--color-border-strong);
}
.lstk-ihead,
.lstk-fhead,
.lstk-frow,
.lstk-find {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-4) var(--spacing-8);
min-width: 0;
}
.lstk-frow {
align-items: center;
}
/* 줄 카드 — 금액 · 글 = 한 줄(요구 687-7 · [손잡이][종류][이름][수량][단위][비목][X] · 좁으면 접힘) · 공식 = 머리 줄 + 식 줄 */
.lstk-line {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-4) var(--spacing-8);
padding: var(--spacing-4) var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface-raised);
}
.lstk-line.k-공식 {
flex-direction: column;
flex-wrap: nowrap;
align-items: stretch;
position: relative;
padding-inline-start: var(--spacing-24);
}
.lstk-line > .lstk-x,
.lstk-fhead > .lstk-x {
margin-inline-start: auto;
}
.lstk-line > .lstk-f-l,
.lstk-line > .lstk-qty {
flex: 1 1 8em;
min-width: 6em;
}
.lstk-line > .lstk-f-m {
flex: 1 1 7em;
min-width: 6em;
width: auto;
}
.lstk-line .lstk-kind {
flex: none;
min-width: 2.5em;
}
.lstk-wide {
flex-basis: 100%;
}
.lstk-qty.ui-btn {
justify-content: flex-start;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.lstk-upick {
flex: none;
}
/* 보이는 글을 눌러 고침(요구 687-14) */
.lstk-tap {
cursor: text;
border-radius: var(--radius-sm);
}
.lstk-tap:hover,
.lstk-tap:focus-visible {
background: var(--color-surface-sunken);
text-decoration: underline dotted;
}
/* 끌기(804-5) — 손잡이 · 끄는 것 반투명 · 놓을 자리 = 위 · 아래 선 · 빈 항목 = 점선 */
.lstk-grip {
touch-action: none;
}
.lstk-line > .lstk-grip {
flex: none;
}
.lstk-line.k-공식 > .lstk-grip {
position: absolute;
inset-block-start: var(--spacing-8);
inset-inline-start: var(--spacing-4);
}
.lstk-edit[data-dragging] {
cursor: grabbing;
user-select: none;
}
.is-dragging {
opacity: 0.45;
}
.lstk-line[data-drop="before"],
.lstk-item[data-drop="before"] {
box-shadow: 0 -3px 0 0 var(--color-accent);
}
.lstk-line[data-drop="after"],
.lstk-item[data-drop="after"] {
box-shadow: 0 3px 0 0 var(--color-accent);
}
.lstk-item[data-drop="in"] .lstk-empty {
outline: 2px dashed var(--color-accent);
outline-offset: 2px;
}
.lstk-res {
flex: 0 1 auto;
min-width: 0;
max-width: 100%;
}
.lstk-kind {
font-size: var(--text-caption);
font-weight: var(--font-weight-medium);
color: var(--color-text-secondary);
}
.lstk-fhead[data-tpl="changed"] .lstk-kind {
color: var(--color-accent);
}
.lstk-f-s {
width: 6em;
}
.lstk-f-m {
width: 9em;
}
.lstk-f-l {
flex: 1 1 12em;
min-width: 10em;
}
.lstk-sym {
font-weight: var(--font-weight-medium);
}
.lstk-val,
.lstk-unit,
.lstk-tag,
.lstk-hint {
font-size: var(--text-body-sm);
color: var(--color-text-secondary);
}
.lstk-tag {
padding: 0 var(--spacing-4);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
}
.lstk-empty {
font-size: var(--text-body-sm);
color: var(--color-text-muted);
}
.lstk-miss,
.lstk-tex .lstk-miss {
color: var(--color-danger);
}
/* ── 오른쪽 산출근거 ── */
.lstk-out .lstk-h {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0 var(--spacing-4);
padding-inline-start: calc(var(--lstk-depth, 0) * var(--spacing-16));
white-space: normal;
}
.lstk-out tr.head,
.lstk-out tr.item,
.lstk-out tr.total {
font-weight: var(--font-weight-medium);
}
.lstk-out tr.subsum,
.lstk-out tr.total {
background: var(--color-surface-sunken);
}
.lstk-out tr.is-sub .lstk-h {
color: var(--color-text-secondary);
}
/* 펼친 문서 제목 = 보통 글(링크 꼴 없음 · 요구 687-11) */
.lstk-out tr.subhead .lstk-h {
font-weight: var(--font-weight-medium);
color: var(--color-text);
}
.lstk-ftitle {
cursor: pointer;
}
.lstk-gap {
display: inline-block;
width: var(--spacing-24);
}
.lstk-pack {
display: inline-flex;
align-items: center;
gap: var(--spacing-4);
}
.lstk-tex .katex {
font-size: 1.05em;
}
/* 짝 강조(679 · 684) — 왼쪽 카드 · 그룹 머리 · 오른쪽 줄 = 같은 바탕 채우기 */
.lstk-line.is-pair,
.lstk-item.is-pair > .lstk-ihead,
.lstk-out tr.is-pair > td {
background: var(--color-accent-soft);
}
.lstk-out tr[data-loc] {
cursor: pointer;
}
.lstk-tpick {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
}
/* 옵션 가로(옛 수식 문서 CSS 에서 옮김 · 804-6) */
.fdoc__options {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-8) var(--spacing-12);
align-items: flex-end;
}
.fdoc__options > * {
width: auto;
min-width: 8em;
}
+80
View File
@@ -0,0 +1,80 @@
/* =============================================================================
* line_stack_drag.ts
* 줄 쌓기 왼쪽 끌어 옮기기(PLAN 804-5 · 요구 682-4) — 손잡이(⋮⋮)를 누르고 끌면 놓을 자리에 선 표시 · 놓으면 `drop`
* 줄 = 항목 안 · 항목 사이 어디든(빈 항목 = 그 안) → `k.p`(항목 k 의 p 자리) · 항목 = 항목 사이 → `p`
* 포인터 끌기(마우스 · 터치 같음) · Esc = 그만 · 자리 표시 = 클래스(`data-drop`)만 — 끄는 동안 배치 안 바뀜
* ========================================================================== */
export interface DragOpts {
/** 끄는 것 — 줄 카드 · 항목 상자 */
what: "line" | "item";
/** 끌기 영역(`.lstk-edit`) */
root: HTMLElement;
/** 끄는 요소(반투명) */
self: HTMLElement;
/** 놓음 — line `k.p` · item `p` */
drop: (to: string) => void;
}
const mid = (r: DOMRect): number => r.top + r.height / 2;
/** 포인터 y 의 놓을 자리 + 표시할 요소 · 꼴 */
function spotOf(o: DragOpts, y: number): { to: string; at: HTMLElement; side: string } | null {
const boxes = [...o.root.querySelectorAll<HTMLElement>(":scope > .lstk-item")];
if (!boxes.length) return null;
if (o.what === "item") {
const k = boxes.findIndex((b) => y < mid(b.getBoundingClientRect()));
return k < 0
? { to: String(boxes.length), at: boxes[boxes.length - 1], side: "after" }
: { to: String(k), at: boxes[k], side: "before" };
}
// 줄 — 포인터가 든 항목(위 · 아래 밖 = 처음 · 끝 항목)
let k = boxes.findIndex((b) => y < b.getBoundingClientRect().bottom);
if (k < 0) k = boxes.length - 1;
const cards = [...boxes[k].querySelectorAll<HTMLElement>(":scope .lstk-lines > .lstk-line")];
if (!cards.length) return { to: `${k}.0`, at: boxes[k], side: "in" };
const p = cards.findIndex((c) => y < mid(c.getBoundingClientRect()));
return p < 0
? { to: `${k}.${cards.length}`, at: cards[cards.length - 1], side: "after" }
: { to: `${k}.${p}`, at: cards[p], side: "before" };
}
/** 손잡이에 끌기를 붙임 */
export function attachDrag(grip: HTMLElement, o: DragOpts): void {
grip.addEventListener("pointerdown", (e) => {
if (e.button !== 0) return;
e.preventDefault();
e.stopPropagation();
grip.setPointerCapture(e.pointerId);
let spot: ReturnType<typeof spotOf> = null;
const clear = (): void => {
for (const x of o.root.querySelectorAll("[data-drop]")) x.removeAttribute("data-drop");
};
o.self.classList.add("is-dragging");
o.root.dataset.dragging = o.what;
const move = (ev: PointerEvent): void => {
clear();
spot = spotOf(o, ev.clientY);
if (spot) spot.at.dataset.drop = spot.side;
};
const end = (ok: boolean): void => {
grip.removeEventListener("pointermove", move);
grip.removeEventListener("pointerup", up);
grip.removeEventListener("pointercancel", cancel);
window.removeEventListener("keydown", esc);
clear();
o.self.classList.remove("is-dragging");
delete o.root.dataset.dragging;
if (ok && spot) o.drop(spot.to);
};
const up = (): void => end(true);
const cancel = (): void => end(false);
const esc = (ev: KeyboardEvent): void => {
if (ev.key === "Escape") end(false);
};
grip.addEventListener("pointermove", move);
grip.addEventListener("pointerup", up);
grip.addEventListener("pointercancel", cancel);
window.addEventListener("keydown", esc);
});
}
+464
View File
@@ -0,0 +1,464 @@
/* =============================================================================
* line_stack_edit.ts
* 줄 쌓기 왼쪽 편집(PLAN 804-2 · 요구 670~677) — 항목(띠 · 번호 · 제목 · 가나다 · [+ 줄] · 접기 · X) 안 줄 카드(⋮⋮ · X)
* 차림(804-5 · 요구 682) = 항목 머리 띠 · 줄 = 들여쓰기 + 왼쪽 세로 선 · 순서 = 손잡이 끌기(`line_stack_drag.ts`) ·
* X = 확인 없이 지우고 알림 눌러 되돌리기 한 번 · 자원 이름 = 단추(누르면 찾아 넣기 창 → 바꿈)
* 줄 = 인력 · 재료 · 기계 · 다른 단가산출 · 율 · 식(금액 줄 — 이름 · 단위 · 비목 · 수량 식 · 단가 고르기 또는 식)
* · 공식 묶음(`line_stack_rows.ts`) · 글 줄
* 고침 = 부른 쪽 `change(fn)` — fn 이 문서 · 차림을 고치면 부른 쪽이 `docOf` 로 굳히고 미리 셈 · 다시 그림(입력은 change 때)
* 자원 고르기(804-3) = 찾아 넣기 창(`line_stack_pick.ts` — M02 [항목 추가] 창 그대로) · [+ 줄] 인력 · 재료 · 기계 · 다른 단가산출 =
* 고르면 줄이 생김(닫으면 안 생김) · 있는 줄 = 자원 이름 단추
* ========================================================================== */
import { createButton, createIconButton } from "@ui/ui_template_button";
import { createRowMenu } from "@ui/ui_template_context_menu";
import { eqText } from "@ui/ui_template_equation";
import { el, showToast } from "@ui/ui_template_elements";
import { createInputField, createSelectField } from "@ui/ui_template_input";
import { varId, type FormulaDoc, type MoneyLine } from "../common_util/common_util_formula_doc";
import { isNum, type DocAnswer } from "../common_util/common_util_formula_doc_calc";
import { lineMap, offLine, type Values } from "../common_util/common_util_formula_doc_view";
import type { DocTable } from "../common_util/common_util_formula_doc_table";
import {
dropSyms,
docOf,
freshId,
kindOf,
moveItem,
moveLine,
newVar,
ownedOf,
stackOf,
type MoneyKind,
type StackDoc,
type StackItem,
} from "../common_util/common_util_line_stack";
import { placeNames } from "../common_util/common_util_line_stack_copy";
import {
addPicked,
aliasText,
repick,
shownUnit,
tableAlias,
} from "../common_util/common_util_line_stack_pick";
import { createUnitPick, NO_UNIT } from "../M02_MasterTemplete/DataFlow/M02_DF_UnitPick";
import { reach } from "./formula_doc_render";
import { attachDrag } from "./line_stack_drag";
import { openStackPick, type StackTake } from "./line_stack_pick";
import { buildBlock } from "./line_stack_rows";
import { lt, type LsKey } from "./line_stack_text";
export interface EditHooks {
doc: StackDoc;
answer: DocAnswer;
items: StackItem[];
change: (fn: (d: StackDoc, items: StackItem[]) => void) => void;
/** 품셈 표 하나(표 고르기 창) */
table: (key: string) => Promise<DocTable | null>;
subDoc: (key: string) => FormulaDoc | null;
name: (key: string) => string;
/** 공식 묶음 펼친 자리(`항목.줄`) — 화면 상태만 */
open: Set<string>;
/** 줄 · 항목 누름 — 오른쪽 짝 강조(679) */
pair: (loc: string) => void;
/** M02 단가산출(804-4) — 다른 단가산출 · 일위대가 = 복사해 펼침 · M01 = 없음(M01 전체만) */
take?: StackTake;
}
const field = (
label: string,
value: unknown,
set: (t: string) => void,
size: "s" | "m" | "l" = "m",
): HTMLElement => {
const f = createInputField({
placeholder: label,
value: value === undefined || value === null ? "" : String(value),
compact: true,
size: 24,
});
f.root.classList.add(`lstk-f-${size}`);
f.input.setAttribute("aria-label", label);
f.input.title = label;
f.input.addEventListener("change", () => set(f.input.value));
return f.root;
};
const btn = (label: string, onClick: () => void) =>
createButton({ label, variant: "ghost", size: 24, onClick });
const KIND_KEY: Record<MoneyKind, LsKey> = {
인력: "K_인력",
재료: "K_재료",
기계: "K_기계",
단가산출: "K_단가산출",
율: "K_율",
};
/** 금액 줄 몸 — 한 줄(804-7 · 요구 687-7): [종류][이름 단추 | 이름 칸 · 단가 식][수량][단위][비목] · 좁으면 접힘 */
function moneyBody(h: EditHooks, i: number): HTMLElement[] {
const d = h.doc;
const line = (d.금액 ?? [])[i];
const kind = kindOf(d, i, h.subDoc);
const own = ownedOf(d).get(i);
const v = own ? d.변수?.find((x) => varId(x) === own) : undefined;
const put = (fn: (l: MoneyLine & { 규격?: string }) => void): void =>
h.change((doc) => fn(doc.금액![i]));
// 표 값 기호 = 화면 이름(요구 683) — 칸 글은 화면 이름 ↔ 기호로 오감
const alias = tableAlias(d);
const shown = (t: unknown): string => aliasText(String(t ?? ""), alias);
const real = (t: string): string => aliasText(t.trim(), alias, true);
const head: HTMLElement[] = [];
if (own && v) {
// 이름 단추(요구 687-9) — 누르면 찾아 넣기 창이 그 이름을 찾은 채 열림 · 고르면 바꿈
const link = v.연결 ?? {};
const mat = link.재료 as Record<string, unknown> | undefined;
const size = (line as { 규격?: string }).규격;
const name = String(line.이름 ?? "") || String(mat?.검색어 ?? mat?.이름 ?? "");
const res = link.키
? link.갈래 === "단가산출"
? (h.subDoc(link.키)?.이름 ?? h.name(link.키))
: h.name(link.키)
: "";
const pick = createButton({
label: (size && !name.includes(size) ? `${name} ${size}` : name) || "—",
variant: "ghost",
tone: "link",
size: 24,
iconStart: "search",
onClick: () =>
openStackPick(
kind,
(p) => h.change((doc) => repick(doc, i, own, kind, p)),
undefined,
String(mat?.검색어 ?? line.이름 ?? ""),
),
});
pick.classList.add("lstk-res");
pick.title = `${lt("Repick")}${res ? ` — ${res}` : ""}`;
pick.dataset.repick = String(i);
head.push(pick);
} else
head.push(
field(lt("Name"), line.이름, (t) => put((l) => (l.이름 = t)), "m"),
field(lt("Price"), shown(line.단가), (t) => put((l) => (l.단가 = real(t))), "l"),
);
const why = (h.answer.줄?.[i] as { 까닭?: string } | undefined)?.까닭;
// 율 밑 번지(복사해 온 조각) — 칸 아래 읽는 이름(804-5)
const places = /번지\./.test(`${line.단가} ${line.수량 ?? ""}`) ? placeNames(d, h.items) : null;
const read = (t: unknown): string =>
shown(t).replace(/번지\.([^.\s()]+)\./g, (all, p: string) =>
places?.[p] ? `${places[p]} ` : all,
);
const unit = el("span", { className: "lstk-upick" });
const drawUnit = (): void =>
unit.replaceChildren(
createUnitPick({
value: String(line.단위 ?? "") || NO_UNIT,
size: 24,
free: true,
ariaLabel: lt("Unit"),
extra: [...new Set((d.금액 ?? []).map((m) => String(m.단위 ?? "")).filter(Boolean))],
onPick: (u) => put((l) => (u === NO_UNIT ? delete l.단위 : (l.단위 = u))),
}),
);
drawUnit();
return [
el("span", { className: "lstk-kind", text: lt(KIND_KEY[kind]) }),
...head,
// 율 · 식 줄(자원 없음)에 수량이 없으면 칸 안 보임(한 줄 유지)
...(own || String(line.수량 ?? "") ? [qtyBox(h, i, shown, real)] : []),
unit,
...(line.비목 === "묶음"
? []
: [
createSelectField({
options: ["노무비", "재료비", "경비"].map((x) => ({ value: x, text: x })),
value: line.비목 ?? "노무비",
compact: true,
size: 24,
ariaLabel: lt("Cost"),
onChange: (x) => put((l) => (l.비목 = x)),
}).root,
]),
...(places
? [
el("div", {
className: "lstk-hint lstk-wide",
text: `= ${[line.수량, line.단가].filter(Boolean).map(read).join(" × ")}`,
}),
]
: []),
...(why ? [el("div", { className: "lstk-miss lstk-wide", text: why })] : []),
];
}
/** 수량 — 기호 하나면 값 + 출처 꼬리표(「0.39 ← 표 · 잡석 수량」 · 요구 687-12 · 기호는 마우스 글) · 누르면 그 자리에서 식 고침 */
function qtyBox(
h: EditHooks,
i: number,
shown: (t: unknown) => string,
real: (t: string) => string,
): HTMLElement {
const line = (h.doc.금액 ?? [])[i];
const text = String(line.수량 ?? "");
const v = h.doc.변수?.find((x) => varId(x) === text);
const at = `qty:${i}`;
const edit = (): HTMLElement => {
const f = field(lt("Qty"), shown(text), (t) =>
h.change((doc) => {
h.open.delete(at);
const l = doc.금액![i];
if (t.trim()) l.수량 = real(t);
else delete l.수량;
}),
);
f.classList.add("lstk-qty");
f.dataset.qty = String(i);
return f;
};
if (!v || h.open.has(at)) return edit();
const val = (h.answer.값 ?? {})[text];
const link = v.연결 ?? {};
const kind = link.갈래 ?? "식";
const from =
v.이름 || (link.키 || link.표 ? h.name(String(link.키 ?? link.표)) : "") || shown(text);
const b = createButton({
label: `${isNum(val) ? eqText(val) : "—"}${v.단위 && shownUnit(v.단위) ? ` ${shownUnit(v.단위)}` : ""} ← ${kind} · ${from}`,
variant: "ghost",
size: 24,
onClick: () => {
h.open.add(at);
h.change(() => undefined);
requestAnimationFrame(() =>
document.querySelector<HTMLInputElement>(`[data-qty="${i}"] input`)?.focus(),
);
},
});
b.classList.add("lstk-qty");
b.title = `${lt("Qty")} = ${shown(text)}`;
b.dataset.qty = String(i);
return b;
}
/** [+ 줄] 고르개 값 → 새 줄 */
function addLine(h: EditHooks, at: number, kind: string): void {
if (["인력", "재료", "기계", "단가산출"].includes(kind))
return openStackPick(
kind as MoneyKind,
(p) => h.change((d, items) => void addPicked(d, items[at], kind as MoneyKind, p)),
h.take,
);
h.change((d, items) => {
const it = items[at];
const n = (d.금액 ??= []).length;
const money = (line: MoneyLine): void => {
d.금액!.push(line);
it.줄.push({ 꼴: "금액", i: n });
};
if (kind === "율")
money({ 이름: "공구손료 및 경장비", 단위: "식", 비목: "경비", 단가: "노무비 × 3 %" });
else if (kind === "공식") {
const id = freshId(d, "x");
(d.변수 ??= []).push(newVar(id, { 갈래: "숫자", 값: 0 }));
it.줄.push({ 꼴: "공식", 기호: [id] });
h.open.add(`${at}.${it.줄.length - 1}`);
} else if (kind === "글") it.줄.push({ 꼴: "글", 글: "" });
});
}
/** 줄 지움 — 금액 줄은 그 줄이 가진 연결 기호까지 · 공식 묶음은 기호까지 */
function dropLine(d: StackDoc, items: StackItem[], i: number, j: number): void {
const line = items[i].줄[j];
if (line.꼴 === "금액") {
const own = ownedOf(d).get(line.i);
if (own) dropSyms(d, [own]);
} else if (line.꼴 === "공식") dropSyms(d, line.기호);
items[i].줄.splice(j, 1);
}
export function buildEdit(h: EditHooks): HTMLElement {
// [+] 줄 더함 메뉴(요구 687-13 · 18) — 인력 · 재료 · 기계 · 단가산출 · 율 · 식 · 근거 글
const add = (at: number): HTMLElement => {
const kinds: [string, LsKey][] = [
["인력", "K_인력"],
["재료", "K_재료"],
["기계", "K_기계"],
["단가산출", h.take ? "K_복사" : "K_단가산출"],
["율", "Add_율"],
["공식", "Add_식"],
["글", "K_글"],
];
const menu = createRowMenu(
kinds.map(([k, label]) => ({ label: lt(label), onClick: () => addLine(h, at, k) })),
{ icon: "plus", title: lt("Add_Line"), size: 24 },
);
menu.classList.add("lstk-add");
return menu;
};
// X 지움 — 확인 없이 · 알림 누르면 되돌림(그 뒤 다른 고침이 없을 때만)
const drop = (what: string, fn: (d: StackDoc, items: StackItem[]) => void): void => {
const snap = JSON.stringify(h.doc);
let after = "";
h.change((d, items) => {
fn(d, items);
after = JSON.stringify(docOf(d, items));
});
showToast(`${what || lt("K_줄")} ${lt("Dropped")}`, "info", 6000, () =>
h.change((d, items) => {
if (JSON.stringify(docOf(d, items)) !== after)
return void showToast(lt("Undo_Late"), "warning");
const back = JSON.parse(snap) as StackDoc;
for (const k of Object.keys(d)) delete (d as Record<string, unknown>)[k];
Object.assign(d, back);
items.splice(0, items.length, ...stackOf(back));
}),
);
};
const x = (title: string, onClick: () => void): HTMLElement => {
const b = createIconButton({ icon: "x", title, size: 24, onClick });
b.classList.add("lstk-x");
return b;
};
const grip = (): HTMLElement =>
el("span", { className: "ui-list-row__grip lstk-grip", attrs: { title: lt("Drag") } });
const root = el("div", { className: "lstk-edit" });
/** 항목 접힘 표시(화면 상태)를 항목 차례 바뀜에 맞춤 */
const refold = (fn: (flags: boolean[]) => void): void => {
const flags = h.items.map((_, k) => h.open.has(`fold:${k}`));
fn(flags);
h.items.forEach((_, k) => h.open.delete(`fold:${k}`));
flags.forEach((f, k) => f && h.open.add(`fold:${k}`));
};
// 항목 번호 — 산출근거와 같은 셈(제목 있는 항목 1. 2. · 옛 가나다 항목 = 가. 나. · 체크 걷음 요구 687-5)
let n = 0;
let sub = 0;
const nos = h.items.map((it) => {
if (!it.제목.trim()) return "";
const no = it.가나다 ? `${"가나다라마바사아자차카타파하"[sub++] ?? "?"}.` : `${++n}.`;
if (!it.가나다) sub = 0;
return no;
});
// 만약(IF) 안 탄 갈래(요구 687-10) — 갈래를 다 열면 닿고 지금 값으로는 안 닿는 기호 · 꺼진 금액 줄 = 숨김(산출근거와 같음)
const vals = (h.answer.값 ?? {}) as Values;
const taken = new Set(reach(h.doc, vals));
const every = new Set(reach(h.doc, {}, true));
const steps = lineMap(h.doc);
const off = (line: StackItem["줄"][number]): boolean =>
line.꼴 === "금액"
? offLine((h.doc.금액 ?? [])[line.i] ?? {}, vals, steps)
: line.꼴 === "공식" &&
line.기호.length > 0 &&
line.기호.every((id) => every.has(id) && !taken.has(id));
const items = h.items.map((it, i) => {
const folded = h.open.has(`fold:${i}`);
const itemGrip = grip();
// 그룹 머리 한 줄(요구 687-6 · 688) — 손잡이 · 접기 · 번호 · 이름 · [+] · X · 접어도 자리 그대로
const head = el("div", {
className: "lstk-ihead",
children: [
itemGrip,
createIconButton({
icon: folded ? "chevronRight" : "chevronDown",
title: folded ? lt("Unfold_Item") : lt("Fold_Item"),
size: 24,
onClick: () => {
if (folded) h.open.delete(`fold:${i}`);
else h.open.add(`fold:${i}`);
h.change(() => undefined);
},
}),
el("b", { className: "lstk-no", text: nos[i] }),
field(lt("Title"), it.제목, (t) => h.change((_, items) => (items[i].제목 = t.trim())), "l"),
el("span", {
className: "lstk-hint lstk-count",
text: folded ? `${lt("K_줄")} ${it.줄.length}` : "",
}),
add(i),
x(lt("Del_Item"), () =>
drop(it.제목.trim() || lt("Item"), (d, items) => {
refold((f) => f.splice(i, 1));
for (let j = items[i].줄.length - 1; j >= 0; j--) dropLine(d, items, i, j);
items.splice(i, 1);
}),
),
],
});
const lines = it.줄.flatMap((line, j) => {
if (off(line)) return [];
const lineGrip = grip();
const del = x(lt("Del"), () =>
drop(line.꼴 === "금액" ? String(h.doc.금액?.[line.i]?.이름 ?? "") : "", (d, items) =>
dropLine(d, items, i, j),
),
);
const body =
line.꼴 === "금액"
? moneyBody(h, line.i)
: line.꼴 === "글"
? [
el("span", { className: "lstk-kind", text: lt("K_글") }),
field(
lt("Text_Ph"),
line.글,
(t) => h.change((_, items) => ((items[i].줄[j] as { 글: string }).글 = t)),
"l",
),
]
: buildBlock(h, i, j, line, (id) => every.has(id) && !taken.has(id));
// 금액 · 글 = 한 줄(X 끝) · 공식 묶음 = 머리 줄 끝에 X
const card = el("div", {
className: `lstk-line k-${line.꼴}`,
children:
line.꼴 === "공식"
? [lineGrip, ...body.slice(0, 1).map((hd) => (hd.append(del), hd)), ...body.slice(1)]
: [lineGrip, ...body, del],
});
card.dataset.loc = `${i}.${j}`;
card.addEventListener("pointerdown", () => h.pair(`${i}.${j}`));
attachDrag(lineGrip, {
what: "line",
root,
self: card,
drop: (to) => {
const [k, p] = to.split(".").map(Number);
h.change((_, items) => moveLine(items, i, j, k, p));
},
});
return [card];
});
const box = el("div", {
className: `lstk-item${it.가나다 ? " is-sub" : ""}${folded ? " is-folded" : ""}`,
attrs: { "data-loc": String(i) },
children: [
head,
...(folded
? []
: [
el("div", {
className: "lstk-lines",
children: lines.length
? lines
: [el("div", { className: "lstk-empty", text: lt("Empty_Item") })],
}),
]),
],
});
head.addEventListener("pointerdown", () => h.pair(String(i)));
attachDrag(itemGrip, {
what: "item",
root,
self: box,
drop: (to) => {
const p = Number(to);
refold((f) => moveItem(f, i, p));
h.change((_, items) => moveItem(items, i, p));
},
});
return box;
});
root.append(
...items,
btn(lt("Add_Item"), () => h.change((_, items) => items.push({ 제목: "", 줄: [] }))),
);
return root;
}
+424
View File
@@ -0,0 +1,424 @@
/* =============================================================================
* line_stack_out.ts
* 줄 쌓기 산출근거 출력(PLAN 804-2 · 요구 670-1 · 677) — 경쟁사 「단가산출」 시트 꼴 표(산출근거 · 합계 · 노무비 · 재료비 · 경비)
* 공식 줄 = 「기호 = 기호식 = 값식 = 결과 단위」(값만인 줄은 넷까지 한 줄) · 금액 줄 = 「이름 : 단가 × 수량 단위 = 금액」
* 기계 · 단가산출 줄 = 비목마다 「노무비 : 59,020 × … = 금액」 — 제목 줄(「굴착기 0.6㎥」) 누름 = 그 문서(기계 시간당 경비 등)를
* 같은 계산기로 셈해 같은 꼴로 펼침(손료 식 · 연료 · 운전사 · 노 · 재 · 경 · 계) · 비목 수는 글(링크 꼴 없음 · 요구 687-11) ·
* 공식 줄 단가산출 결과 = 수 누름(▸) · 접으면 한 줄 · 출력엔 펼친 것만
* 값 = 계산기 셈 답 그대로(`calcDoc` · 줄 `answer.줄` · 합 `노무비` …) — 여기서 다시 셈 안 함 · 조판 = KaTeX(`line_stack_tex.ts`)
* ========================================================================== */
import { createButton, createIconButton } from "@ui/ui_template_button";
import { eqText } from "@ui/ui_template_equation";
import { el } from "@ui/ui_template_elements";
import { tableRow, tableShell, type Column } from "@ui/ui_template_table";
import { COSTS, varId, type FormulaDoc } from "../common_util/common_util_formula_doc";
import { calcDoc, isNum, type DocAnswer } from "../common_util/common_util_formula_doc_calc";
import type { DocCache } from "../common_util/common_util_formula_doc_cache";
import {
lineMap,
moneyScope,
offLine,
type Values,
} from "../common_util/common_util_formula_doc_view";
import { reach } from "./formula_doc_render";
import {
kindOf,
nodeOf,
priceSym,
stackOf,
subInputs,
type StackDoc,
} from "../common_util/common_util_line_stack";
import { placeNames } from "../common_util/common_util_line_stack_copy";
import { shownUnit, stackLevels, tableAlias } from "../common_util/common_util_line_stack_pick";
import { katexNode, symTex, tex, valTex, type TexCtx } from "./line_stack_tex";
import { lt } from "./line_stack_text";
type Cols = Partial<Record<"sum" | (typeof COSTS)[number], number>>;
interface Row {
h: (string | HTMLElement)[];
cols?: Cols;
cls?: string;
won?: boolean;
depth: number;
/** 들여쓰기 수준(804-3) — 머리 0 · 항목 1 · 가나다 2 · 줄 = 항목 + 1 · 펼친 블록 = 그 줄 + 1 */
lv: number;
/** 왼쪽 짝 자리(679) — 항목 `k` · 줄 `k.j`(펼친 블록 = 그 줄) */
loc?: string;
}
export interface OutHooks {
/** 줄 누름 — 왼쪽 짝 자리(679) */
pair?: (loc: string) => void;
cache: DocCache;
/** 펼친 자리(`부모/기호`) */
open: Set<string>;
toggle: (at: string) => void;
/** 캐시에 없는 연결 — 뒤에서 서버에 물음 */
miss: () => void;
}
const COST_SHOW: Record<string, string> = { 노무비: "노무비", 재료비: "재료비", 경비: "경 비" };
const won = (n: number): string => eqText(Math.round(n));
const one = (n: number): string =>
n.toLocaleString("en-US", { minimumFractionDigits: 1, maximumFractionDigits: 1 });
const MAX_DEPTH = 3;
const LONG_SUM = 4; // 합계 덧셈 항이 이보다 많으면 항목 소계끼리(경쟁사 「노무비 : 2,523.3 + 919.5 = 3,442」)
/** 식 글 → 조판(못 풀면 글) */
function exprNode(
text: unknown,
side: "sym" | "val",
vals: Values,
inline = false,
cost?: string,
more: Partial<TexCtx> = {},
) {
const n = nodeOf(text);
return n
? katexNode(tex(n, { side, vals, inline, cost, ...more }))
: el("span", { text: String(text ?? "") });
}
const hasVar = (text: unknown): boolean => {
const n = nodeOf(text);
return !!n && JSON.stringify(n).includes('["var"');
};
function rows(
doc: StackDoc,
a: DocAnswer,
h: OutHooks,
depth: number,
at: string,
base = 0,
): Row[] {
const out: Row[] = [];
let lv = base + 1; // 지금 쌓는 줄 수준
let loc: string | undefined; // 지금 쌓는 왼쪽 자리(맨 위 문서만)
const vals = (a.값 ?? {}) as Values;
const vars = new Map((doc.변수 ?? []).map((v) => [varId(v), v]));
const lines = new Map((doc.식 ?? []).map((l) => [String(l.기호 ?? ""), l.식]));
// 펼친 문서 줄 = 금액 칸 없이(부른 쪽 칸과 섞이지 않게)
const push = (r: Omit<Row, "depth" | "lv">): void =>
void out.push({ ...r, depth, lv, loc, ...(depth ? { cols: undefined } : {}) });
const shown = new Set(reach(doc, vals)); // 지금 탄 갈래로 닿는 기호만
const steps = lineMap(doc);
const place = placeNames(doc, stackOf(doc)); // 번지 읽는 이름(804-5)
const unit = (id: string): string => shownUnit(vars.get(id)?.단위);
const alias = tableAlias(doc); // 표 값 기호 화면 이름(요구 683)
const subOf = (id: string): FormulaDoc | null => {
const k = vars.get(id)?.연결;
if (k?.갈래 !== "단가산출" || !k.키 || k.키.includes("{")) return null;
try {
return h.cache.links.doc(k.키) ?? null;
} catch {
if (h.cache.takeMissing()) h.miss();
return null;
}
};
/** ▸ 단추 — 펼친 자리면 그 문서 줄을 뒤에 끼움 */
const opener = (id: string, label: string): HTMLElement => {
const path = `${at}/${id}`;
const b = createButton({
label: `${label} ${h.open.has(path) ? "▾" : "▸"}`,
variant: "link",
size: 20,
onClick: () => h.toggle(path),
});
b.dataset.expand = path;
return b;
};
/** 기계 · 단가산출 제목 줄 — 접기 단추 + 제목(누르면 펼침 · 요구 687-11) */
const fold = (id: string, title: string): HTMLElement[] => {
const path = `${at}/${id}`;
const on = h.open.has(path);
const b = createIconButton({
icon: on ? "chevronDown" : "chevronRight",
title: lt(on ? "Fold_Sub" : "Unfold_Sub"),
size: 20,
onClick: (e) => (e.stopPropagation(), h.toggle(path)),
});
b.dataset.expand = path;
const t = el("span", { className: "lstk-ftitle", text: title });
t.addEventListener("click", (e) => (e.stopPropagation(), h.toggle(path)));
return [b, t];
};
const expand = (id: string): void => {
const path = `${at}/${id}`;
const sub = subOf(id);
if (!h.open.has(path) || !sub || depth >= MAX_DEPTH) return;
const got = calcDoc(sub, h.cache.links, subInputs(vars.get(id)?.연결, vals));
if (h.cache.takeMissing()) h.miss();
out.push({
depth: depth + 1,
lv: lv + 1,
loc,
h: [`${sub.이름 ?? ""}${sub.결과단위 ? ` [${sub.결과단위}]` : ""}`],
cls: "subhead",
});
out.push(...rows(sub, got, h, depth + 1, path, lv + 1).map((r) => ({ ...r, loc })));
};
/** 공식 묶음 — 값만인 줄은 넷까지 한 줄 */
const block = (ids: string[]): void => {
let pack: HTMLElement[] = [];
const flush = (): void => {
if (pack.length)
push({ h: pack.flatMap((x, k) => (k ? [el("span", { className: "lstk-gap" }), x] : [x])) });
pack = [];
};
for (const id of ids.filter((x) => shown.has(x))) {
const v = vals[id];
const t = lines.get(id);
const head = katexNode(symTex(alias[id] ?? id));
if (t !== undefined && hasVar(t)) {
flush();
const res = isNum(v)
? katexNode(`${valTex(v)}\\ ${unit(id) ? `\\text{${unit(id)}}` : ""}`)
: missing(a, id);
// 기호식 = 값식 = 결과 — 같은 꼴이 이어지면 한 번만(만약이 수 하나를 탄 줄)
const n = nodeOf(t)!;
const sym = tex(n, { side: "sym", vals, alias });
const val = tex(n, { side: "val", vals, alias });
const same = isNum(v) && val === valTex(v);
push({
h: [
head,
...(sym === val ? [] : [" = ", katexNode(sym)]),
...(same ? [] : [" = ", katexNode(val)]),
" = ",
res,
],
});
continue;
}
const sub = subOf(id);
if (sub || (v && typeof v === "object")) {
flush();
const label =
v && typeof v === "object"
? COSTS.map((c) => `${COST_SHOW[c]} ${eqText((v as Values)[c])}`).join(" · ")
: isNum(v)
? `${eqText(v)} ${unit(id) || sub?.결과단위 || ""}`
: "—";
push({
h: [head, " = ", sub ? opener(id, label) : label, sub ? ` ${sub.이름 ?? ""}` : ""],
});
expand(id);
continue;
}
const expr =
t !== undefined && !isNum(Number(t))
? [" = ", exprNode(t, "sym", vals, false, undefined, { alias })]
: [];
const cell = el("span", {
className: "lstk-pack",
children: [
head,
...expr,
" = ",
isNum(v) ? `${eqText(v)} ${unit(id)}` : typeof v === "string" ? v : missing(a, id),
],
});
pack.push(cell);
if (pack.length >= 4) flush();
}
flush();
};
const money = doc.금액 ?? [];
const lineRows = (i: number): { sub: boolean; cols: Cols } => {
const line = money[i];
const r = (a.줄?.[i] ?? {}) as {
비목?: Record<string, number>;
금액?: number | null;
까닭?: string;
단가?: unknown;
};
const scope: Values = { ...vals, ...moneyScope(a.줄, i) };
const at: Record<string, Values> = {}; // 앞 줄 번지 합(계산기와 같음 — 번지 값을 값 식에 · 804-5)
money.slice(0, i).forEach((m, k) => {
const got = a.줄?.[k] as { 금액?: unknown; 비목?: Values } | undefined;
if (m.기호) scope[m.기호] = got?.금액; // 앞 금액 줄 기호 = 그 줄 금액
if (m.번지)
for (const c of COSTS) {
const box = (at[String(m.번지)] ??= { 노무비: 0, 재료비: 0, 경비: 0 });
if (isNum(got?.비목?.[c]))
box[c] = Math.round(((box[c] as number) + (got!.비목![c] as number)) * 1e6) / 1e6;
}
});
if (Object.keys(at).length) scope.번지 = at;
const name = [line.이름, (line as { 규격?: string }).규격].filter(Boolean).join(" ");
const qty =
line.수량 !== undefined && line.수량 !== null && line.수량 !== "" ? line.수량 : null;
const qtyNode = (cost?: string): (string | HTMLElement)[] =>
qty === null
? []
: [
" × ",
exprNode(qty, "val", scope, true, cost, { alias }),
line.단위 ? ` ${line.단위}` : "",
];
if (r.까닭 && r.금액 === null) {
push({ h: [`${name} : — `, el("span", { className: "lstk-miss", text: r.까닭 })] });
return { sub: false, cols: {} };
}
if (line.비목 === "묶음") {
const s = priceSym(line);
const bundle = (r.단가 ?? {}) as Values;
const cols: Cols = {};
const title = name || (s ? (subOf(s)?.이름 ?? s) : "");
push({ h: s && subOf(s) ? fold(s, title) : [title], cls: "lname" });
for (const c of COSTS) {
const part = r.비목?.[c];
if (!isNum(part) || (!part && !bundle[c])) continue;
cols[c] = part;
push({
h: [`${COST_SHOW[c]} : `, eqText(bundle[c]), ...qtyNode(c), ` = ${one(part)}`],
});
}
if (s) expand(s);
return { sub: true, cols };
}
const amt = r.금액 ?? 0;
const price =
hasVar(line.단가) && kindOf(doc, i) === "율"
? [
exprNode(line.단가, "sym", scope, true, undefined, { place, alias }),
" = ",
exprNode(line.단가, "val", scope, true, undefined, { alias }),
]
: [exprNode(line.단가, "val", scope, true, undefined, { alias })];
const c = String(line.비목);
push({
h: [`${name} : `, ...price, ...qtyNode(), ` = ${one(amt)}`],
cols: { sum: amt, [c]: amt },
});
return { sub: false, cols: { [c]: amt } };
};
const items = stackOf(doc);
const terms: Record<string, number[]> = { 노무비: [], 재료비: [], 경비: [] };
const perItem: Record<string, number[]> = { 노무비: [], 재료비: [], 경비: [] }; // 덧셈이 길면 항목 소계끼리(804-5)
let n = 0;
let sub = 0;
const levels = stackLevels(items);
for (const [k, it] of items.entries()) {
lv = base + levels[k].item;
loc = depth ? undefined : String(k);
if (it.제목.trim()) {
const label = it.가나다 ? `${"가나다라마바사아자차카타파하"[sub++] ?? "?"} . ` : `${++n}. `;
if (!it.가나다) sub = 0;
push({ h: [label + it.제목], cls: it.가나다 ? "item sub" : "item" });
}
const sum: Record<string, number> = { 노무비: 0, 재료비: 0, 경비: 0 };
const own: Record<string, number[]> = { 노무비: [], 재료비: [], 경비: [] };
let hasSub = false;
lv = base + levels[k].line;
for (const [j, l] of it.줄.entries()) {
loc = depth ? undefined : `${k}.${j}`;
if (l.꼴 === "글") push({ h: [l.글] });
else if (l.꼴 === "공식") block(l.기호);
else {
if (offLine(money[l.i], vals, steps)) continue; // 선택으로 꺼진 줄(수식 탭과 같음)
const got = lineRows(l.i);
hasSub ||= got.sub;
if ((a.줄?.[l.i] as { 합밖?: boolean } | undefined)?.합밖) continue;
for (const [c, v] of Object.entries(got.cols)) {
sum[c] = Math.round((sum[c] + (v ?? 0)) * 10) / 10;
if (v) own[c].push(v);
}
}
}
loc = depth ? undefined : String(k);
if (hasSub && COSTS.some((c) => sum[c])) {
const s = Math.round((sum.노무비 + sum.재료비 + sum.경비) * 10) / 10;
push({ h: [`${lt("Sub_Sum")} : ${one(s)}`], cols: { sum: s, ...sum }, cls: "subsum" });
}
for (const c of COSTS) {
terms[c].push(...(hasSub ? (sum[c] ? [sum[c]] : []) : own[c]));
if (sum[c]) perItem[c].push(sum[c]);
}
}
lv = base + 1;
loc = undefined;
if (doc.결과) {
const r = a.결과;
push({
h: [
katexNode(symTex(doc.결과)),
` = ${isNum(r) ? `${eqText(r)} ${doc.결과단위 ?? ""}` : "—"}`,
],
cls: "total",
});
return out;
}
if (!money.length) return out;
push({ h: [`${++n}. ${lt("Total")}`], cls: "item" });
lv = base + 2;
for (const c of COSTS) {
const t = terms[c].length > LONG_SUM ? perItem[c] : terms[c];
const v = a[c];
const body = !isNum(v)
? "—"
: t.length > 1
? `${t.map(one).join(" + ")} = ${won(v)}`
: t.length
? won(v)
: "";
push({ h: [`${COST_SHOW[c]} : ${body}`] });
}
push({
h: [`${lt("Sum")} : ${isNum(a.계) ? won(a.계) : "—"}`],
cols: isNum(a.계)
? { sum: a.계, 노무비: a.노무비 ?? 0, 재료비: a.재료비 ?? 0, 경비: a.경비 ?? 0 }
: {},
cls: "total",
won: true,
});
return out;
}
const missing = (a: DocAnswer, id: string): HTMLElement =>
el("span", { className: "lstk-miss", text: `— ${a.빈값?.[id] ?? ""}`.trim() });
/** 산출근거 표 */
export function buildOut(doc: StackDoc, a: DocAnswer, h: OutHooks): HTMLElement {
const columns: Column[] = [
{ label: lt("Basis") },
{ label: lt("Total"), width: "92px", align: "end" },
{ label: COST_SHOW.노무비, width: "84px", align: "end" },
{ label: COST_SHOW.재료비, width: "84px", align: "end" },
{ label: COST_SHOW.경비, width: "84px", align: "end" },
];
const { root, tbody } = tableShell(columns, { density: "data" });
const head: Row = {
h: [`${doc.이름 ?? ""}${doc.결과단위 ? ` [${doc.결과단위}]` : ""}`],
cols: isNum(a.계)
? { sum: a.계, 노무비: a.노무비 ?? 0, 재료비: a.재료비 ?? 0, 경비: a.경비 ?? 0 }
: {},
cls: "head",
won: true,
depth: 0,
lv: 0,
};
for (const r of [head, ...rows(doc, a, h, 0, "")]) {
const cell = (k: "sum" | (typeof COSTS)[number]): string => {
const v = r.cols?.[k];
if (v === undefined || (r.won && !v && k !== "sum")) return "";
return r.won ? won(v) : one(v);
};
const first = el("div", { className: "lstk-h", children: r.h });
first.style.setProperty("--lstk-depth", String(r.lv));
first.dataset.lv = String(r.lv);
const tr = tableRow(columns, [first, cell("sum"), ...COSTS.map(cell)]);
tr.className = `lstk-orow ${r.cls ?? ""} ${r.depth ? "is-sub" : ""}`;
if (r.loc !== undefined) {
tr.dataset.loc = r.loc;
tr.addEventListener("click", () => h.pair?.(r.loc!));
}
tbody.append(tr);
}
root.classList.add("lstk-out");
return root;
}
+157
View File
@@ -0,0 +1,157 @@
/* =============================================================================
* line_stack_pick.ts
* 줄 쌓기 고르기 창(PLAN 804-3 · 804-4 · 요구 678-1 · 678-3 · 680-1 · 681)
* 찾아 넣기 = M02 [항목 추가] 창 그대로(`openFindPicker` · 탭 M01 전체 · 찾기 칸 · 표) — 줄 갈래마다 그 종류만
* 인력(노임 LB) · 재료(자재 MT) · 기계(EQ → 시간당 경비 로직 + 기계='원문번호') · 다른 단가산출(M01 단가산출 로직)
* M01 단가산출 기초 줄 쌓기 = 「M01 전체」 탭만 — M01 은 원천 기준값이라 M02 단가산출 · 일위대가를 부르지 않음(요구 680-1)
* M02 단가산출 줄 쌓기(`take` 있음) · 다른 단가산출 = 탭 [M01 전체 | M02 단가산출 | 일위대가](위 층 · 자기 SK 뺌) —
* 고름 = 복사해 펼침(`take(src)` · src = M01 키 | `참조|층|주인|키`) · 참조 줄 없음(요구 681)
* 표 고르기 = 품셈 표(조건 칸 + 값 칸) · 값 칸 누름 = 그 줄 · 칸 · 지금 닿는 칸 강조 ·
* 옵션에 매인 조건 = 그 줄만 고를 수 있음(다른 줄은 막힘 + 「위 옵션에서 고름」 안내)
* ========================================================================== */
import { createButton } from "@ui/ui_template_button";
import { eqText } from "@ui/ui_template_equation";
import { el } from "@ui/ui_template_elements";
import { openModal } from "@ui/ui_template_modal";
import { tableRow, tableShell, type Column } from "@ui/ui_template_table";
import { createText } from "@ui/ui_template_text";
import type { DocTable } from "../common_util/common_util_formula_doc_table";
import type { MoneyKind } from "../common_util/common_util_line_stack";
import type { Picked } from "../common_util/common_util_line_stack_pick";
import { showToast } from "@ui/ui_template_elements";
import type { Layer } from "../M02_MasterTemplete/M02_MasterTemplete_Api_Fetch";
import { openFindPicker, type FindKind } from "../M02_MasterTemplete/M02_MasterTemplete_Find_Pick";
import { lt } from "./line_stack_text";
const KINDS: Partial<Record<MoneyKind, FindKind>> = {
인력: "labor",
재료: "material",
기계: "machine",
단가산출: "logic",
};
/** M02 단가산출 복사 길(804-4) — 그 SK 층 · 키 · 복사해 넣기 */
export interface StackTake {
layer: Layer;
owner: string | null;
me: string;
take: (src: string) => void;
}
/** 찾아 넣기 — 고르면 `done(고른 것)` · 닫으면 아무것도 안 함 · `m02` 있고 다른 단가산출 = 복사해 펼침 ·
* `query` = 찾기 칸에 넣고 연 결과(이름 단추 · 요구 687-9) */
export function openStackPick(
kind: MoneyKind,
done: (p: Picked) => void,
m02?: StackTake,
query = "",
): void {
const want = KINDS[kind];
if (!want) return;
const spec = (e: { 규격: string }) => (e.규격 ? { 규격: e.규격 } : {});
const copy = kind === "단가산출" && m02 ? m02 : null;
openFindPicker({
layer: copy?.layer ?? "system",
owner: copy?.owner ?? null,
title: `${lt("Find_Title")} — ${lt(copy ? "K_복사" : (`K_${kind}` as never))}`,
kinds: copy ? ["logic", "sk", "pre"] : [want], // M01 = M01 전체만(680-1)
query: query.trim() || undefined,
ref: copy
? (r) =>
r.키 === copy.me && r.층 === copy.layer && (!copy.owner || r.주인 === copy.owner)
? showToast(lt("Self_Take"), "warning")
: copy.take(`참조|${r.층}|${r.주인 ?? ""}|${r.키}`)
: undefined,
logic: (x) =>
copy
? copy.take(x.키)
: done({ 키: x.키, 이름: x.이름, 단위: x.결과단위.replace(/^원\/?/, "") }),
element: (e) => done({ 키: e.코드, 이름: e.이름, 단위: e.단위, ...spec(e) }),
machine: (e) =>
e.로직 && e.원문번호
? done({ 키: e.로직, 이름: e.이름, 입력: { 기계: `'${e.원문번호}'` }, ...spec(e) })
: undefined,
});
}
const isNum = (v: unknown): boolean =>
typeof v === "number" || (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)));
const cellText = (v: unknown): string =>
Array.isArray(v)
? `${v[0] ?? ""} ~ ${v[1] ?? ""}`
: isNum(v)
? eqText(Number(v))
: String(v ?? "");
export interface TablePick {
table: DocTable;
/** 지금 닿는 줄(-1 = 없음) · 칸 */
now: number;
col: string;
/** 옵션에 매인 조건 — {조건 칸: 옵션 열쇠들} */
bound: Record<string, string[]>;
done: (row: Record<string, unknown>, col: string) => void;
}
/** 표 고르기 창 — 값 칸 누름 = 고름 */
export function openTablePick(o: TablePick): void {
const t = o.table;
const conds = Object.keys(t.조건 ?? {});
const valueCols = [
...new Set([...Object.keys(t.값칸 ?? {}), ...t.줄.flatMap((r) => Object.keys(r))]),
].filter((k) => !conds.includes(k));
const locked = Object.keys(o.bound).length > 0;
const columns: Column[] = [
...conds.map((c) => ({ label: c })),
...valueCols.map((c) => {
const unit = (t.값칸 ?? {})[c];
return { label: unit ? `${c} (${String(unit)})` : c, align: "end" as const };
}),
];
openModal({
title: [t.원문번호, (t as { 이름?: string }).이름].filter(Boolean).join(" "),
closeLabel: lt("Close"),
width: "960px",
mount: (body, close) => {
const { root, tbody } = tableShell(columns, { density: "data" });
t.줄.forEach((row, k) => {
const off = locked && k !== o.now;
const tr = tableRow(columns, [
...conds.map((c) => cellText(row[c])),
...valueCols.map((c) =>
isNum(row[c]) // 수만 고름(글 칸 = 단위 등은 보기만 · M02 길은 수를 글로 줌)
? createButton({
label: cellText(row[c]),
variant: k === o.now && c === o.col ? "filled" : "ghost",
size: 24,
disabled: off,
onClick: () => {
close();
o.done(row, c);
},
})
: cellText(row[c]),
),
]);
tr.dataset.row = String(k);
tbody.append(tr);
});
const note = locked
? [
createText(
`${lt("Tbl_Bound")} — ${[...new Set(Object.values(o.bound).flat())].join(" · ")}`,
),
]
: [];
body.append(
el("div", {
className: "lstk-tpick",
attrs: { "data-table-pick": t.키 ?? "" },
children: [...note, createText(lt("Tbl_Hint")), root],
}),
);
},
});
}
+327
View File
@@ -0,0 +1,327 @@
/* =============================================================================
* line_stack_rows.ts
* 줄 쌓기 공식 묶음(PLAN 804-2 · 요구 675) — 「이름 = 값」 · 「이름 = 식」 줄 묶음 · 틀 이름표 · [식으로 풀기] · [틀로 저장]
* 보임 = 값 줄은 값 칸(연결 값을 고쳐 적으면 숫자 = 고정값 · 요구 664) · 식 줄은 「이름 = 식 = 값」 글 · 단가산출 결과 기호 = [식으로 풀기]
* 고침(804-7 · 요구 687-14) = [식 고치기] 걷음 — 이름 · 식 글을 누르면 그 줄만 그 자리에서 이름 · 식 · 단위 칸 + 지움 ·
* 바꾸면 닫힘 · 머리 [+] = 식 줄 더함 — 이름 바꿈 = 문서 전부(`rename`) · 식 = 식 줄 · 수 = 숫자 연결
* 만약 안 탄 갈래 기호(`hide` · 요구 687-10) = 안 보임(식 글은 그대로)
* 틀 = 단가산출 결과 문서(굴착기 Q 등)를 이 문서 식으로 옮김(`unfold`) — 꼴이 바뀌면 「내 틀 (틀 이름 에서 변경)」 ·
* 빈 틀 = [찾아 넣기](M01 단가산출 결과 — 작업량 등) · [틀로 저장] · 내 틀 목록 걷음(요구 685) · 표 값만 = 머리 「표 값」
* 표 값(804-3) = [표 · 칸] 단추 → 표 고르기 창(`line_stack_pick.ts`) · 값 칸 누름 = 그 줄 · 칸(`pickCell`)
* ========================================================================== */
import { createButton, createIconButton } from "@ui/ui_template_button";
import { eqText } from "@ui/ui_template_equation";
import { el, showToast } from "@ui/ui_template_elements";
import { createInputField } from "@ui/ui_template_input";
import {
DocError,
rename,
splitId,
varId,
type FormulaDoc,
} from "../common_util/common_util_formula_doc";
import { isNum } from "../common_util/common_util_formula_doc_calc";
import {
dropSyms,
freshId,
newVar,
setRow,
shapeOf,
unfold,
type StackItem,
type StackLine,
} from "../common_util/common_util_line_stack";
import {
aliasText,
boundConds,
currentRow,
pickCell,
shownUnit,
tableAlias,
} from "../common_util/common_util_line_stack_pick";
import { openFindPicker } from "../M02_MasterTemplete/M02_MasterTemplete_Find_Pick";
import type { EditHooks } from "./line_stack_edit";
import { openTablePick } from "./line_stack_pick";
import { lt } from "./line_stack_text";
const clone = <T>(v: T): T => JSON.parse(JSON.stringify(v)) as T;
type Block = Extract<StackLine, { 꼴: "공식" }>;
const LINK_TAG: Record<string, string> = {
표: "표",
요소: "요소",
단가산출: "단가산출",
선택: "선택",
입력: "입력",
};
const input = (
value: string,
placeholder: string,
cls: string,
set: (t: string) => void,
): HTMLElement => {
const f = createInputField({ value, placeholder, compact: true, size: 24 });
f.root.classList.add(cls);
f.input.addEventListener("change", () => set(f.input.value));
return f.root;
};
export function buildBlock(
h: EditHooks,
i: number,
j: number,
line: Block,
hide: (id: string) => boolean = () => false,
): HTMLElement[] {
const d = h.doc;
const at = `${i}.${j}`;
const editing = (id: string): boolean => h.open.has(`${at}:${id}`);
/** 그 줄 고침 켬 · 끔(화면 상태) */
const flip = (id: string, on: boolean): void => {
if (on) h.open.add(`${at}:${id}`);
else h.open.delete(`${at}:${id}`);
};
const vals = (h.answer.값 ?? {}) as Record<string, unknown>;
const vars = new Map((d.변수 ?? []).map((v) => [varId(v), v]));
const lines = new Map((d.식 ?? []).map((l) => [String(l.기호 ?? ""), String(l.식 ?? "")]));
const blockOf = (items: StackItem[]): Block => items[i].줄[j] as Block;
// 표 값 기호 = 화면 이름(요구 683 · 문서 기호 그대로) — 칸 글은 화면 이름 ↔ 기호로 오감
const alias = tableAlias(d);
const show = (id: string): string => alias[id] ?? id;
const shownText = (t: string): string => aliasText(t, alias);
const realText = (t: string): string => aliasText(t, alias, true);
const valText = (id: string): string => {
const v = vals[id];
if (isNum(v) || typeof v === "string")
return `${eqText(v)} ${shownUnit(vars.get(id)?.단위)}`.trim();
if (v && typeof v === "object")
return ["노무비", "재료비", "경비"]
.map((c) => `${c} ${eqText((v as Record<string, unknown>)[c])}`)
.join(" · ");
return `— ${h.answer.빈값?.[id] ?? ""}`.trim();
};
// ── 이름표 ──
const tplName = (k: string): string =>
k.startsWith("my:") ? lt("My_Tpl") : (h.subDoc(k)?.이름 ?? (h.name(k) || k)); // my: = 옛 내 틀(걷음 · 요구 685)
const changed = !!line.틀 && !!line.틀꼴 && shapeOf(d, line.기호) !== line.틀꼴;
const tableOnly = line.기호.every((id) => vars.get(id)?.연결?.갈래 === "표" && !lines.has(id));
const label = !line.틀
? lt(tableOnly && line.기호.length ? "K_표값" : "K_공식")
: changed
? `${lt("My_Tpl")} (${tplName(line.틀)} ${lt("Changed_From")})`
: tplName(line.틀);
const head = el("div", {
className: "lstk-fhead",
children: [
el("span", { className: "lstk-kind", text: label }),
createIconButton({
icon: "plus",
title: lt("Add_Row"),
size: 24,
onClick: () =>
h.change((doc, items) => {
const id = freshId(doc, "x");
(doc.변수 ??= []).push(newVar(id, { 갈래: "숫자", 값: 0 }));
blockOf(items).기호.push(id);
flip(id, true);
}),
}),
],
});
head.dataset.tpl = changed ? "changed" : line.틀 ? "tpl" : "";
// ── 표 값 — [표 · 칸] → 표 고르기 ──
const tableBtn = (id: string, link: NonNullable<FormulaDoc["변수"]>[number]["연결"] & object) => {
const b = createButton({
label:
[
lt("Tbl"),
...Object.values(link.조건 ?? {}).flatMap((t) =>
/^'.*'$/.test(String(t)) ? [String(t).slice(1, -1)] : [],
),
link.칸,
]
.filter(Boolean)
.join(" · ") + " ▾", // 고른 줄(글 조건) · 칸
variant: "ghost",
size: 24,
onClick: () =>
void h.table(String(link.표 ?? "")).then((t) => {
if (!t) return void showToast(lt("Tbl_Missing"), "warning");
openTablePick({
table: t,
now: currentRow(t, link, vals),
col: String(link.칸 ?? ""),
bound: boundConds(d, link),
done: (row, col) => h.change((doc) => pickCell(doc, id, t, row, col)),
});
}),
});
b.dataset.tablePick = id;
b.title = h.name(String(link.표 ?? "")) || String(link.표 ?? "");
return b;
};
// ── 줄 ──
const renameRow = (old: string, now: string): void => {
const t = now.trim();
if (!t || t === old) return;
try {
const copy = clone(d);
const [s, lo, up] = splitId(t);
rename(copy, old, s, lo, up);
} catch (e) {
if (e instanceof DocError) return void showToast(lt("Bad_Name"), "warning");
throw e;
}
h.change((doc, items) => {
const [s, lo, up] = splitId(t);
rename(doc, old, s, lo, up);
for (const it of items)
for (const l of it.줄) if (l.꼴 === "공식") l.기호 = l.기호.map((x) => (x === old ? t : x));
});
};
const unfoldRow = (id: string, sub: FormulaDoc, key: string): void =>
h.change((doc, items) => {
const moved = unfold(doc, sub, id);
const blk = blockOf(items);
blk.기호 = blk.기호.filter((x) => x !== id);
items[i].줄.splice(j + 1, 0, { 꼴: "공식", 기호: moved, 틀: key, 틀꼴: shapeOf(doc, moved) });
if (!blk.기호.length) items[i].줄.splice(j, 1);
});
/** 누르면 그 줄 고침(보이는 글을 고침) */
const tap = (node: HTMLElement, id: string): HTMLElement => {
node.classList.add("lstk-tap");
node.tabIndex = 0;
node.setAttribute("role", "button");
node.title = lt("Edit_Hint");
const go = (): void => {
flip(id, true);
h.change(() => undefined);
};
node.addEventListener("click", go);
node.addEventListener("keydown", (e) => e.key === "Enter" && go());
return node;
};
/** 고친 뒤 닫음 */
const done = (id: string, fn: () => void): void => {
flip(id, false);
fn();
};
const rows = line.기호
.filter((id) => !hide(id))
.map((id) => {
const v = vars.get(id);
const link = v?.연결;
const expr = lines.get(id);
const sub = link?.갈래 === "단가산출" && link.키 ? h.subDoc(link.키) : null;
const tag =
link?.갈래 === "표" && !expr
? tableBtn(id, link)
: link && LINK_TAG[link.갈래 ?? ""] && !expr
? el("span", {
className: "lstk-tag",
text: `${LINK_TAG[link.갈래!]}${link.키 && !link.키.includes("{") ? ` · ${sub?.이름 ?? h.name(link.키) ?? ""}` : ""}`,
})
: null;
const tools: HTMLElement[] = [];
if (sub?.결과 && link?.키)
tools.push(
createButton({
label: lt("Unfold"),
variant: "ghost",
size: 24,
onClick: () => unfoldRow(id, sub, link.키!),
}),
);
if (link?.갈래 === "단가산출" && !link.키)
tools.push(
createButton({
label: lt("Find_Title"),
variant: "ghost",
size: 24,
onClick: () =>
openFindPicker({
layer: "system",
owner: null,
title: lt("K_단가산출"),
kinds: ["logic"],
results: true,
logic: (x) =>
h.change((doc) => {
const y = doc.변수?.find((z) => varId(z) === id);
if (y) y.연결 = { 갈래: "단가산출", 키: x.키, 입력: {} };
}),
}),
}),
);
const name = tap(el("b", { className: "lstk-sym", text: show(id) }), id);
const valueOf =
expr ??
(link?.갈래 === "숫자" ? String(link.값 ?? "") : isNum(vals[id]) ? String(vals[id]) : "");
let body: (HTMLElement | string)[];
const open = editing(id);
if (open)
body = [
input(show(id), lt("Row_Name"), "lstk-f-s", (t) =>
done(id, () => renameRow(id, t.trim() === show(id) ? id : t)),
),
"=",
input(shownText(valueOf), lt("Row_Expr"), "lstk-f-l", (t) =>
done(id, () => h.change((doc) => setRow(doc, id, realText(t)))),
),
input(v?.단위 ?? "", lt("Unit"), "lstk-f-s", (t) =>
done(id, () =>
h.change((doc) => {
let x = doc.변수?.find((y) => varId(y) === id);
if (!x) (doc.변수 ??= []).push((x = newVar(id, { 갈래: "식" })));
x.단위 = t.trim();
}),
),
),
el("span", { className: "lstk-val", text: `= ${valText(id)}` }),
createButton({
label: "×",
variant: "danger-ghost",
size: 24,
onClick: () => done(id, () => h.change((doc) => dropSyms(doc, [id]))),
}),
createIconButton({
icon: "check",
title: lt("Close"),
size: 24,
onClick: () => done(id, () => h.change(() => undefined)),
}),
];
else if (expr !== undefined || sub || link?.갈래 === "단가산출")
body = [
name,
"=",
...(expr
? [tap(el("span", { className: "lstk-expr", text: shownText(expr) }), id), "="]
: []),
el("span", { className: "lstk-val", text: valText(id) }),
];
else
body = [
name,
"=",
input(shownText(valueOf), valText(id), "lstk-f-m", (t) =>
h.change((doc) => setRow(doc, id, realText(t))),
),
...(shownUnit(v?.단위)
? [el("span", { className: "lstk-unit", text: shownUnit(v?.단위) })]
: []),
];
const row = el("div", {
className: `lstk-frow ${open ? "is-open" : ""}`,
children: [...body, ...(tag ? [tag] : []), ...tools],
});
row.dataset.sym = id;
return row;
});
return [head, ...rows];
}
+496
View File
@@ -0,0 +1,496 @@
/* =============================================================================
* line_stack_screen.ts
* 단가산출 「줄 쌓기」 탭(PLAN 804-2 · 요구 670~677) — 공용(M01 단가산출 기초 · M02 단가산출 = 길 앞머리만 바꿈)
* 위 = 옵션 가로(`formula_doc_options.ts`) · M02 사본 [단가 갱신] · [재생성] · 「M01 새 판」 배지(층 쓰기)
* `page` 있음(M01 804-7 · M02 804-8) = 저장 · 되돌림 단추 없음 — 페이지 [저장] · [되돌림]이 `save` · `discard` 를 부름 ·
* 고침이 바뀌면 `page()` · `page` 없음(일위대가 줄 모달 등) = 위에 [고침 버림] · [수식 저장]
* 가운데 = 왼쪽 줄 쌓기 편집(`line_stack_edit.ts`) | 오른쪽 산출근거 출력(`line_stack_out.ts` · 기계 시간당 경비 펼침)
* 아래 = 합계 상자(노무비 · 재료비 · 경비 · 계 · 화면 아래 고정 · 요구 687-3)
* 정본 = 수식 문서(탭을 바꾸면 다시 읽음) · 줄 쌓기 차림 = 문서 칸 `쌓기` · 804-6 옛 수식 문서 탭 걷음 = 이 화면이 「수식」 탭
* 셈 = 브라우저 계산기(`calcDoc`) 즉시 · 캐시에 없는 연결만 뒤에서 `POST {길}/preview`(옛 요청 끊음)
* 고침 = 캐시(sessionStorage)에 쌓고 [저장] 한 번에 `POST {길}/save`(서버가 정본으로 다시 셈) · 자동저장 없음
* M02 사본(`copy`) — 저장 몸 {문서, 끊음(받은 그대로), 판} · 판 바뀜(409)은 문서가 그대로면 새 판으로 한 번 더
* [단가 갱신] · [재생성] = 미리 셈 → 알림 → 저장(`formula_doc_copy.ts` · 고친 것 먼저 저장)
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
import { createBadge } from "@ui/ui_template_badge";
import { createButton } from "@ui/ui_template_button";
import {
el,
hideLoadingOverlay,
showConfirmDialog,
showLoadingOverlay,
showToast,
} from "@ui/ui_template_elements";
import { blankDoc, type FormulaDoc } from "../common_util/common_util_formula_doc";
import { calcDoc, type DocAnswer } from "../common_util/common_util_formula_doc_calc";
import type { DocTable } from "../common_util/common_util_formula_doc_table";
import { createDocCache, type DocRecord } from "../common_util/common_util_formula_doc_cache";
import {
docOf,
stackOf,
type StackDoc,
type StackItem,
} from "../common_util/common_util_line_stack";
import {
askJob,
copyBadges,
copyTools,
type CopyJob,
type CopyMark,
type CopyOptions,
type JobReply,
} from "./formula_doc_copy";
import { docFoot } from "./formula_doc_foot";
import { buildOptions } from "./formula_doc_options";
import { ft } from "./formula_doc_text";
import { addTaken, type Taken } from "../common_util/common_util_line_stack_copy";
import type { Layer } from "../M02_MasterTemplete/M02_MasterTemplete_Api_Fetch";
import { buildEdit } from "./line_stack_edit";
import { buildOut } from "./line_stack_out";
import { lt } from "./line_stack_text";
import "./line_stack.css";
export interface StackScreenOptions {
/** 길 앞머리(`/m01/logic/doc`) — GET ?key · POST /preview · /save · /links */
base: string;
admin: boolean;
/** sessionStorage 열쇠 */
cache: string;
copy?: CopyOptions;
/** M02 SK 층(804-4) — 줄 쌓기 다른 단가산출 · 일위대가 복사 길 · 없으면(M01) 복사 없음 */
scope?: { layer: Layer; owner: string | null };
/** 페이지가 저장 · 되돌림을 맡음(M01 804-7 · M02 804-8) — 고침 있음이 바뀔 때 부름 · 화면 위 [고침 버림] · [수식 저장] 없음 */
page?: () => void;
}
type Docs = Record<string, FormulaDoc>;
interface Loaded {
문서: StackDoc;
셈: DocAnswer;
연결: DocRecord;
새?: boolean;
끊음?: Docs;
사본?: CopyMark;
판?: string;
}
const clone = <T>(v: T): T => JSON.parse(JSON.stringify(v)) as T;
export function createStackScreen(opt: StackScreenOptions) {
const head = el("div", { className: "lstk__head" });
const left = el("div", { className: "lstk__left" });
const right = el("div", { className: "lstk__right" });
const foot = el("div", { className: "lstk__foot" });
const root = el("div", {
className: "lstk",
attrs: { "data-line-stack": "" },
children: [head, el("div", { className: "lstk__areas", children: [left, right] }), foot],
});
// 산출근거 고정 높이(요구 683) — 스크롤 조상(없으면 창) 높이 · 넓을 때만 고정(line_stack.css)
const fit = (): void => {
let p = root.parentElement;
while (p && !/(auto|scroll)/.test(getComputedStyle(p).overflowY)) p = p.parentElement;
const view = (p?.clientHeight ?? innerHeight) - foot.offsetHeight;
root.style.setProperty("--lstk-view", `${Math.max(240, view)}px`);
};
window.addEventListener("resize", fit);
const cp = opt.copy;
let key = "";
let shown: string | null = null; // 연 키(같은 키 다시 그림 = 안 읽음 · 탭 바꿈 = force)
let saved = "";
let doc: StackDoc | null = null;
let answer: DocAnswer | null = null;
let cache = createDocCache();
let given: Record<string, unknown> = {};
let cut: Docs = {};
let mark: CopyMark | undefined;
let version = "";
let seq = 0;
let asked = "";
let ctl: AbortController | null = null;
const tables = new Map<string, Promise<DocTable | null>>(); // 표 고르기(문서 열 때 비움)
const opened = new Set<string>(); // 산출근거 펼친 자리
let paired = ""; // 짝 강조 자리(679 — 항목 `k` · 줄 `k.j`)
const blocks = new Set<string>(); // 풀린 공식 묶음
const call = async <T>(path: string, body?: unknown, signal?: AbortSignal): Promise<T> => {
const r = await fetch(`${API_BASE_URL}${cp ? cp.route(key, path) : opt.base + path}`, {
method: body === undefined ? "GET" : "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
signal,
});
const data = (await r.json().catch(() => ({}))) as T & { detail?: unknown };
if (!r.ok) {
const why = data.detail;
throw Object.assign(new Error(typeof why === "string" ? why : r.statusText), {
status: r.status,
});
}
return data;
};
// ── 캐시 ──
const drafts = (): Record<string, FormulaDoc> => {
try {
return JSON.parse(sessionStorage.getItem(opt.cache) ?? "{}") as Record<string, FormulaDoc>;
} catch {
return {};
}
};
const dirty = (): boolean => !!doc && JSON.stringify(doc) !== saved;
const keep = (): void => {
const all = drafts();
if (dirty()) all[key] = doc!;
else delete all[key];
try {
sessionStorage.setItem(opt.cache, JSON.stringify(all));
} catch {
/* 캐시가 막혀도 화면 안 고침은 남음 */
}
opt.page?.();
};
// ── 셈 ──
const usable = (): Record<string, unknown> => {
const out: Record<string, unknown> = {};
for (const v of doc?.변수 ?? []) {
const k = v.열쇠;
if (!k || !(k in given)) continue;
const kind = v.연결?.갈래;
if (kind === "입력") out[k] = given[k];
if (kind === "선택" && (v.연결?.목록 ?? []).some((x) => String(x) === String(given[k])))
out[k] = (v.연결!.목록 ?? []).find((x) => String(x) === String(given[k]));
}
return out;
};
const recalc = (): void => {
if (!doc) return;
answer = calcDoc(doc, cache.links, usable());
if (cache.takeMissing()) ask();
};
/** 캐시에 없는 연결 — 뒤에서 서버 기록을 받아 더함 */
const ask = (): void => {
const snap = JSON.stringify([doc, usable()]);
if (snap === asked) return;
asked = snap;
ctl?.abort();
ctl = new AbortController();
const mine = ++seq;
void call<{ 연결: DocRecord }>(
"/preview",
cp ? { 문서: doc, 끊음: cut } : { 문서: key ? doc : { ...doc, 키: "_새" }, 입력: usable() },
ctl.signal,
)
.then((got) => {
if (mine !== seq) return;
cache.merge(got.연결);
cache.merge({ 문서: cut });
recalc();
draw();
})
.catch(() => undefined);
};
const subDoc = (k: string): FormulaDoc | null => {
try {
return cache.links.doc(k) ?? null;
} catch {
if (cache.takeMissing()) ask();
return null;
}
};
/** 표 하나(`/table`) — 받은 표는 계산기 캐시에도 */
const tableOf = (k: string): Promise<DocTable | null> => {
if (!tables.has(k))
tables.set(
k,
call<DocTable & { 끊김?: string }>(`/table?key=${encodeURIComponent(k)}`)
.then((t) => {
if (t.끊김) return null;
cache.putTable(k, t);
return t;
})
.catch(() => null),
);
return tables.get(k)!;
};
/** 고침 — 문서 · 차림을 고친 뒤 문서로 굳힘 */
const change = (fn: (d: StackDoc, items: StackItem[]) => void): void => {
if (!doc) return;
const items = stackOf(doc);
fn(doc, items);
doc = docOf(doc, items);
keep();
recalc();
draw();
};
/** 짝 강조 — 왼쪽 카드 · 오른쪽 줄(같은 자리) · from = 누른 쪽(반대쪽을 보이는 자리로) */
const pair = (loc: string, from?: "left" | "right"): void => {
paired = loc;
for (const x of root.querySelectorAll(".is-pair")) x.classList.remove("is-pair");
if (!loc) return;
const sel = `[data-loc="${CSS.escape(loc)}"]`;
const l = left.querySelector<HTMLElement>(sel);
const r = [...right.querySelectorAll<HTMLElement>(sel)];
l?.classList.add("is-pair");
r.forEach((x) => x.classList.add("is-pair"));
if (from === "right") l?.scrollIntoView({ block: "nearest", behavior: "smooth" });
if (from === "left") r[0]?.scrollIntoView({ block: "nearest", behavior: "smooth" });
};
/** 복사해 펼침(804-4) — 서버 조각 → 새 항목(고침 = 캐시 · [저장] 때 서버가 다시 셈) */
const takeIn = async (src: string): Promise<void> => {
try {
const got = await call<Taken>(`/take?src=${encodeURIComponent(src)}`);
change((d, items) => addTaken(d, items, got));
} catch (e) {
showToast(`${lt("Take_Failed")} — ${e instanceof Error ? e.message : ""}`, "error");
}
};
const takeCtx = () =>
cp && opt.scope && key
? { ...opt.scope, me: key, take: (src: string) => void takeIn(src) }
: undefined;
// ── 그림 ──
const edit = (): boolean => opt.admin && (!!key || !cp);
const draw = (): void => {
if (!doc || !answer) {
[head, left, right, foot].forEach((x) => x.replaceChildren());
return;
}
const options = buildOptions(doc, given, !!cp && !opt.admin, (k, v, x) => {
const at = doc?.변수?.indexOf(v) ?? -1;
if (cp) return change((d) => at >= 0 && (d.변수![at].연결 = { ...v.연결!, 값: x }));
given[k] = x;
recalc();
draw();
});
const busy = dirty();
const tools = [
...(cp && edit() && key ? copyTools(true, (job) => void runJob(job)) : []),
...(edit() && key && !opt.page
? [
createButton({
label: ft("Discard"),
variant: "ghost",
size: 28,
disabled: !busy,
onClick: () => void discard(),
}),
createButton({
label: ft("Save"),
variant: "filled",
size: 28,
disabled: !busy,
onClick: () => void save(),
}),
]
: []),
];
head.replaceChildren(
...(options.length ? [el("div", { className: "fdoc__options", children: options })] : []),
el("div", {
className: "lstk__bar",
children: [
el("div", {
className: "lstk__badges",
children: [
...(busy ? [createBadge(ft("Dirty"), "warning")] : []),
...(cp ? copyBadges(mark, opt.admin) : []),
],
}),
el("div", { className: "lstk__tools", children: tools }),
],
}),
);
const items = stackOf(doc);
root.dataset.edit = edit() ? "1" : "";
left.replaceChildren(
...(edit()
? [
buildEdit({
doc,
answer,
items,
change,
table: tableOf,
subDoc,
name: (k) => cache.names.get(k) ?? "",
open: blocks,
pair: (loc) => pair(loc, "left"),
take: takeCtx(),
}),
]
: []),
);
right.replaceChildren(
items.length || doc.결과
? buildOut(doc, answer, {
cache,
open: opened,
toggle: (at) => {
if (opened.has(at)) opened.delete(at);
else opened.add(at);
draw();
},
miss: ask,
pair: (loc) => pair(loc, "right"),
})
: el("p", { className: "ui-text ui-text--muted", text: lt("Empty") }),
);
foot.replaceChildren(...(doc.결과 ? [] : [docFoot(answer)]));
pair(paired);
fit();
};
const take = (got: Loaded): void => {
cache.merge(got.연결);
saved = JSON.stringify(got.문서);
doc = clone(got.문서);
cut = clone(got.끊음 ?? {});
mark = got.사본;
version = got.판 ?? "";
};
/** 사본 [단가 갱신] · [재생성] — 미리 셈 → 알림 → 저장(고친 것 먼저 저장 · 버림) */
const runJob = async (job: CopyJob): Promise<void> => {
if (!doc || !cp) return;
if (dirty()) return void showToast(ft("Dirty_First"), "warning");
showLoadingOverlay();
let got: JobReply;
try {
got = await call<JobReply>("/preview", { 할일: job });
} catch (e) {
hideLoadingOverlay();
return void showToast(
`${ft("Save_Failed")} — ${e instanceof Error ? e.message : ""}`,
"error",
);
}
hideLoadingOverlay();
if (await askJob(job, got, cache.names)) await save({ 할일: job, 판: version }, ft("Job_Done"));
};
let loading: Promise<void> = Promise.resolve();
const save = async (job?: Record<string, unknown>, done = ft("Saved")): Promise<boolean> => {
await loading; // 새 로직 저장 뒤 여는 중이면 다 읽고(캐시 이어 받음)
if (!doc || (!job && !dirty())) return true;
const send = job ?? (cp ? { 문서: doc, 끊음: cut, 판: version } : { 문서: doc });
const was = saved;
showLoadingOverlay();
try {
const got = await call<Loaded>("/save", send).catch(async (e: { status?: number }) => {
// 사본 판 바뀜(페이지 [저장] 등) — 문서가 받은 그대로면 새 판으로 한 번 더
if (!cp || e.status !== 409) throw e;
const now = await call<Loaded>("");
if (JSON.stringify(now.문서) !== was) throw e;
return call<Loaded>("/save", { ...send, 판: (version = now.판 ?? "") });
});
take(got);
keep();
recalc();
draw();
if (cp && got.판) cp.onSaved?.(got.판);
if (done) showToast(done, "success");
return true;
} catch (e) {
showToast(`${ft("Save_Failed")} — ${e instanceof Error ? e.message : ""}`, "error");
return false;
} finally {
hideLoadingOverlay();
}
};
const discard = async (): Promise<void> => {
if (!doc || !dirty()) return void draw();
doc = JSON.parse(saved) as StackDoc;
keep();
recalc();
draw();
};
/** 문서 열기 — 서버 · 캐시에서 다시 읽음(수식 탭 고침 · 저장을 이어 받음) */
const open = async (next: string): Promise<void> => {
const was = key;
shown = next;
if (next !== key) {
paired = "";
opened.clear();
blocks.clear();
given = {};
}
key = next;
doc = answer = null;
asked = "";
cache = createDocCache();
tables.clear();
[cut, mark, version] = [{}, undefined, ""];
draw();
if (!next) {
if (cp)
return void right.replaceChildren(
el("p", { className: "ui-text ui-text--muted", text: lt("Unsaved_New") }),
);
doc = (drafts()[""] as StackDoc | undefined) ?? blankDoc();
saved = JSON.stringify(blankDoc());
recalc();
return draw();
}
const mine = ++seq;
try {
const got = await call<Loaded>(cp ? "" : `?key=${encodeURIComponent(next)}`);
if (mine !== seq || key !== next) return;
take(got);
const all = drafts();
if (!was && got.새 && all[""] && !all[next]) {
all[next] = { ...all[""], 키: next, 이름: got.문서.이름, 결과단위: got.문서.결과단위 };
delete all[""];
sessionStorage.setItem(opt.cache, JSON.stringify(all));
}
if (all[next]) doc = clone(all[next] as StackDoc);
recalc();
draw();
opt.page?.(); // 캐시에서 이어 받은 고침 — 페이지 [저장] · [되돌림] 켬
} catch (e) {
if (mine === seq)
head.replaceChildren(
el("p", {
className: "ui-text ui-text--muted",
text: `${ft("Load_Failed")} — ${e instanceof Error ? e.message : ""}`,
}),
);
}
};
const canLeave = async (): Promise<boolean> => {
if (!dirty()) return true;
if (!(key in drafts())) return true; // 다른 화면에서 이미 버림
if (!(await showConfirmDialog(ft("Leave"), ft("Leave_Ok")))) return false;
doc = JSON.parse(saved) as StackDoc;
keep();
return true;
};
return {
root,
/** 로직 · SK 키로 그림(빈 키 = 저장 전 새 것) — 같은 키는 그대로 · `force` = 다시 읽음(탭 바꿈) */
draw: (next: string, force = false): void => {
if (force || next !== shown) loading = open(next);
},
canLeave,
dirty,
/** 페이지 [저장](804-7) — 고침 있으면 서버 정본 저장 · 알림은 페이지가(`done` 빈 글) · 실패 = false */
save: (): Promise<boolean> => save(undefined, ""),
/** 페이지 [되돌림] — 저장 뒤 상태로(캐시 지움) */
discard: (): Promise<void> => discard(),
/** 시험 · 확인용 */
state: () => ({ doc, answer, dirty: dirty(), items: doc ? stackOf(doc) : [] }),
};
}
+152
View File
@@ -0,0 +1,152 @@
/* =============================================================================
* line_stack_tex.ts
* 줄 쌓기 산출근거 수식 조판(PLAN 804-2 · 요구 677) — 식 나무 → TeX → KaTeX(MIT) · 분수 · 첨자 · 괄호
* 기호 식(side "sym") = 기호(첨자) · 값 식(side "val") = 값(모르면 기호 빨강) · 만약 = 탄 갈래만 · 반올림 · 올림 · 버림 감싸개 숨김
* 금액 줄 꼴(`inline`) = ÷ 한 줄(경쟁사 「노무비 : 59,020 ÷ Q」) · 공식 줄 = 분수
* 규칙은 수식 탭 그림(`formula_doc_render.ts` renderNode)과 같음 — 셈 정본은 계산기
* ========================================================================== */
import katex from "katex";
import "../config/node_modules/katex/dist/katex.min.css";
import { eqText } from "@ui/ui_template_equation";
import { splitId, type Node } from "../common_util/common_util_formula_doc";
import { isNum } from "../common_util/common_util_formula_doc_calc";
import { leafNum, takenOf, type Values } from "../common_util/common_util_formula_doc_view";
export interface TexCtx {
side: "sym" | "val";
vals: Values;
inline?: boolean;
/** 묶음 값의 비목(기계 · 단가산출 줄 노 · 재 · 경) */
cost?: string;
/** 번지 읽는 이름(804-5) — 「번지.복사N.노무비」 → 「항목 이름 노무비」 */
place?: Record<string, string>;
/** 표 값 기호 화면 이름(요구 683 — 「Q_수량2^표」 → 「Q_작업반장」) */
alias?: Record<string, string>;
}
const HIDE = new Set(["반올림", "올림", "버림"]);
const OPS: Record<string, string> = {
"+": "+",
"-": "-",
"*": "\\times",
"/": "\\div",
"<": "<",
"<=": "\\le",
">": ">",
">=": "\\ge",
"==": "=",
"!=": "\\ne",
그리고: "\\;\\text{그리고}\\;",
또는: "\\;\\text{또는}\\;",
};
const PREC: Record<string, number> = {
또는: 1,
그리고: 2,
"<": 3,
"<=": 3,
">": 3,
">=": 3,
"==": 3,
"!=": 3,
"+": 4,
"-": 4,
"*": 5,
"/": 5,
};
const prec = (n: Node): number => (n[0] === "bin" ? PREC[n[1]] : n[0] === "neg" ? 6 : 9);
/** TeX 글 안전 — \text{} 안 */
const txt = (s: string): string => `\\text{${s.replace(/[\\{}$&#^_%~]/g, (c) => `\\${c}`)}}`;
const word = (s: string): string =>
/^[A-Za-z]$/.test(s) ? s : /^[A-Za-z]+$/.test(s) ? `\\mathit{${s}}` : txt(s);
/** 기호 글(`Q_작업^표`) → TeX(첨자) */
export function symTex(id: string): string {
const [s, lo, up] = splitId(id);
return `${word(s)}${lo ? `_{${word(lo)}}` : ""}${up ? `^{${word(up)}}` : ""}`;
}
/** 값 → TeX(천 자리 쉼표) */
export const valTex = (v: unknown): string => {
const t = eqText(v);
return isNum(v) ? t.replace(/,/g, "{,}") : txt(t);
};
/** 칸 값 — `번지.복사1.노무비` 처럼 겹친 칸까지 */
const look = (n: Node, vals: Values): unknown => {
if (n[0] === "var") return vals[n[1]];
if (n[0] !== "field") return undefined;
const v = look(n[1], vals);
return v && typeof v === "object" ? (v as Values)[n[2]] : undefined;
};
const paren = (t: string): string => `\\left(${t}\\right)`;
const miss = (t: string): string => `\\htmlClass{lstk-miss}{${t}}`;
export function tex(n: Node, c: TexCtx): string {
const go = (x: Node): string => tex(x, c);
switch (n[0]) {
case "num":
return valTex(n[1]);
case "str":
return txt(`'${n[1]}'`);
case "var": {
const id = c.alias?.[n[1]] ?? n[1];
if (c.side === "sym") return symTex(id);
let v = c.vals[n[1]];
if (v && typeof v === "object" && c.cost) v = (v as Values)[c.cost];
return v === null || v === undefined || typeof v === "object" ? miss(symTex(id)) : valTex(v);
}
case "field": {
const [, of, key] = n;
if (c.side === "val") {
const got = look(n, c.vals);
if (isNum(got)) return valTex(got);
}
if (of[0] === "field" && of[1][0] === "var" && of[1][1] === "번지" && c.place?.[of[2]])
return txt(`${c.place[of[2]]} ${key}`);
return `${n[1][0] === "var" ? symTex(n[1][1]) : go(n[1])}.${txt(n[2])}`;
}
case "neg":
return `-${n[1][0] === "bin" ? paren(go(n[1])) : go(n[1])}`;
case "pct":
return `${prec(n[1]) < 9 ? paren(go(n[1])) : go(n[1])}\\,\\%`;
case "call": {
const [, name, items] = n;
if (name === "만약") {
const pick = takenOf(n, c.vals);
if (pick) return go(items[pick]);
}
if (HIDE.has(name) && items.length >= 1 && items.length <= 2) return go(items[0]);
const fn = name === "최소" ? "\\min" : name === "최대" ? "\\max" : txt(name);
return `${fn}${paren(items.map(go).join(",\\,"))}`;
}
case "bin": {
const [, o, a, b] = n;
const na = leafNum(a, c.vals);
const nb = leafNum(b, c.vals);
if ((o === "+" || o === "-") && nb === 0) return go(a);
if ((o === "*" || o === "/") && nb === 1) return go(a);
if ((o === "+" && na === 0) || (o === "*" && na === 1)) return go(b);
if (o === "/" && !c.inline) return `\\dfrac{${go(a)}}{${go(b)}}`;
const mine = PREC[o];
const left = prec(a) < mine || (mine === 3 && prec(a) === 3) ? paren(go(a)) : go(a);
const right = prec(b) <= mine ? paren(go(b)) : go(b);
return `${left} ${OPS[o] ?? txt(o)} ${right}`;
}
}
}
/** TeX → 조판 요소(못 그리면 글 그대로) */
export function katexNode(t: string): HTMLElement {
const box = document.createElement("span");
box.className = "lstk-tex";
katex.render(t, box, {
throwOnError: false,
strict: "ignore",
trust: (x) => x.command === "\\htmlClass",
output: "html",
});
return box;
}
+78
View File
@@ -0,0 +1,78 @@
/* =============================================================================
* line_stack_text.ts
* 줄 쌓기 화면 글(PLAN 804-2) — [국문, 영문] · 수식 탭과 같은 글은 `formula_doc_text.ts`
* ========================================================================== */
import { currentLanguageIndex } from "@ui/ui_template_locale";
const TEXT = {
Tab: ["줄 쌓기", "Line stack"],
Basis: ["산 출 근 거", "Basis"],
Total: ["합 계", "Total"],
Sum: ["계", "Sum"],
Sub_Sum: ["소 계", "Subtotal"],
Add_Item: ["+ 그룹", "+ Group"],
Add_Line: ["줄 더함", "Add line"],
Add_율: ["율 — 그룹 소계의 %", "Rate — % of subtotal"],
Add_식: ["식 — 공식 틀", "Formula"],
Del_Item: ["그룹 지움", "Delete group"],
Del: ["지움", "Delete"],
Item: ["그룹", "Group"],
K_줄: ["줄", "Line"],
Drag: ["끌어 옮기기", "Drag to move"],
Fold_Item: ["그룹 접기", "Collapse group"],
Unfold_Item: ["그룹 펼치기", "Expand group"],
Unfold_Sub: ["시간당 경비 · 산출 펼치기", "Show breakdown"],
Fold_Sub: ["접기", "Hide breakdown"],
Dropped: ["지움 — 눌러 되돌리기", "deleted — click to undo"],
Undo_Late: ["그 뒤 고친 것이 있어 못 되돌림", "Changed since — cannot undo"],
Title: ["그룹 이름", "Group title"],
K_인력: ["인력", "Labor"],
K_재료: ["재료", "Material"],
K_기계: ["기계", "Machine"],
K_단가산출: ["단가산출", "Unit price"],
K_복사: ["단가산출 · 일위대가 복사", "Copy unit price · combo"],
Self_Take: ["지금 단가산출 자신은 못 가져옴", "Cannot copy itself"],
Take_Failed: ["복사 못 함", "Copy failed"],
K_율: ["율", "Rate"],
K_공식: ["공식", "Formula"],
K_표값: ["표 값", "Table values"],
K_글: ["근거 글", "Basis note"],
Name: ["이름", "Name"],
Unit: ["단위", "Unit"],
Cost: ["비목", "Cost"],
Qty: ["수량", "Qty"],
Price: ["단가", "Price"],
Machine: ["기계", "Machine"],
Find: ["찾기", "Find"],
Pick: ["고름 ▾", "Pick ▾"],
None_Found: ["찾은 것 없음", "Nothing found"],
Unfold: ["식으로 풀기", "Unfold"],
Edit_Hint: ["눌러 고침", "Click to edit"],
Add_Row: ["식 줄 더함", "Add row"],
New_Formula: ["새 공식", "New formula"],
My_Formula: ["내 공식", "My formula"],
My_Tpl: ["내 틀", "My template"],
Changed_From: ["에서 변경", "changed"],
Row_Name: ["이름", "Name"],
Row_Expr: ["값 또는 식", "Value or expr"],
Bad_Name: ["못 쓰는 이름 · 같은 이름 있음", "Bad or duplicate name"],
Empty_Item: ["줄 없음 — [+]", "No lines — [+]"],
Empty: ["빈 단가산출 — [+ 그룹] 으로 시작", "Empty — start with [+ Group]"],
Text_Ph: ["< 품셈 근거 > · ※ 메모 · 별산", "Note"],
Unsaved_New: ["저장한 단가산출만 — 페이지 [저장] 뒤", "Saved unit prices only"],
Find_Title: ["찾아 넣기", "Find and add"],
Repick: ["찾아 바꾸기", "Find and change"],
Close: ["닫기", "Close"],
Tbl: ["표", "Table"],
Tbl_Missing: ["표를 못 읽음", "Table not found"],
Tbl_Hint: ["값 칸을 누르면 그 값이 들어감", "Click a value cell to use it"],
Tbl_Bound: [
"이 값의 줄은 위 옵션이 고름(다른 줄은 옵션에서 바꿈 · 여기선 칸만)",
"Row follows the option above (change it there; pick a column here)",
],
} as const;
export type LsKey = keyof typeof TEXT;
export const lt = (key: LsKey): string => TEXT[key][currentLanguageIndex === 1 ? 1 : 0];
+62 -17
View File
@@ -1,7 +1,8 @@
/* =============================================================================
* m_settings_nav.ts
* 시스템 설정 바로가기(334 · 요구 225-2) — 대시보드 「시스템 설정」 카드 · M01 · M02 ☰ 가 같이 쓰는
* 역할 규칙 한 벌(`settingLinks`) · ☰ 진행 단계 자리에 🏠 대시보드 + 카드(`mountSettingsDrawer`).
* 시스템 설정 바로가기(334 · 요구 225-2 · 716-2) — 대시보드 설계 기본값 줄(`settingLinks` · 칸마다 한 줄 ·
* `?open=`) / M01 · M02 ☰ 메뉴(`pageLinks` · 페이지 둘 · `mountSettingsDrawer`) /
* 진행 판 없는 쪽 ☰ 기본 목록(`defaultSettingsBar` · 772-1).
* ========================================================================== */
import { ROUTES, type RoutePath } from "@config/config_frontend";
@@ -16,52 +17,65 @@ interface SettingUser {
company_id?: number | null;
}
/** key = M01 은 「m01」 · 계산 자리 창은 「calcSites」(대시보드 카드만 · 504-2) · M02 는 진입 인자 `open` */
/** key = 페이지 열쇠(☰ · 「m01」 「m02」) 또는 칸 열쇠(대시보드 줄 · 계산 자리 「calcSites」 = 창 504-2) ·
* group = 대시보드 설계 기본값 묶음 제목 */
export interface SettingLink {
key: string;
title: string;
desc: string;
route: RoutePath;
query?: Record<string, string>;
group?: string;
}
function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
const ICONS: Record<string, string> = { m01: "🗄️", 구조물조합: "🧱", 옵션: "🎛️", 도면: "📑" };
const ICONS: Record<string, string> = { m01: "🗄️", m02: "🧱" };
/** 시스템 설정 — 역할로 카드 숨김 · M02 카드는 `?open=&root=` 로 진입(27-1d) */
/** 설계 기본값 줄(대시보드) — 칸마다 한 줄 · 역할로 숨김 · M01 줄 `?open=`(칸 고름) · M02 줄 `?open=&root=`(27-1d).
* 칸을 더할 때는 아래 표에 한 줄(m01 / m02 호출)만 더함. */
export function settingLinks(user: SettingUser, companyId: number | string | null): SettingLink[] {
// 시스템 관리자는 고른 회사를 해시에 실음(29-1)
const company: Record<string, string> =
user.role === "SYSTEM_ADMIN" && companyId ? { company: String(companyId) } : {};
const m01 = (key: string, title: string, desc: string, open?: string): SettingLink => ({
key,
title,
desc,
route: ROUTES.M01_MASTER_DATA,
query: open ? { open } : undefined,
group: "마스터 데이터",
});
const m02 = (title: string, open: string, root: string, desc: string): SettingLink => ({
key: open,
title,
desc,
route: ROUTES.M02_MASTER_TEMPLATE,
query: { open, root, ...company },
group: "마스터 템플릿",
});
// 구조물 조합 기본 층 = 개인(123-1 · 누구든) · 개인 층 없음(회사 없음) = 아래 역할 규칙
const comboRoot = (fallback: string): string => (user.company_id ? "personal" : fallback);
if (user.role === "SYSTEM_ADMIN") {
const desc = L("B01_Dashboard_PermMasterWrite");
const read = L("B01_Dashboard_PermSourceRead");
return [
{
key: "m01",
title: L("B01_Dashboard_MasterElements"),
desc: L("B01_Dashboard_PermSourceRead"),
route: ROUTES.M01_MASTER_DATA,
},
m01("m01", L("M01_Title"), read),
m01("로직", L("B01_Dashboard_CardLogic"), read, "로직"),
m01("옵션", L("B01_Dashboard_CardStructOptionDef"), desc, "옵션"),
m01("별칭", L("B01_Dashboard_CardAliasLedger"), desc, "별칭"),
m02(L("B01_Dashboard_CardSangeun"), "단가산출", comboRoot("master"), desc),
m02(L("B01_Dashboard_CardPrecombo"), "일위대가조합", comboRoot("master"), desc),
m02(L("B01_Dashboard_CardStructCombo"), "구조물조합", comboRoot("master"), desc),
m02(L("B01_Dashboard_CardStructOption"), "옵션", "master", desc),
m02(L("B01_Dashboard_CardDrawing"), "도면", "master", desc),
{
key: "calcSites",
title: L("B01_CalcSites_Title"),
desc: L("B01_CalcSites_Desc"),
route: ROUTES.B01_ACCOUNT,
group: "계산",
},
];
}
@@ -76,15 +90,47 @@ export function settingLinks(user: SettingUser, companyId: number | string | nul
return [m02(L("B01_Dashboard_CardStructCombo"), "구조물조합", comboRoot(root), desc)];
}
/** ☰ 진행 단계 자리 — 🏠 대시보드 + 카드 · 지금 칸 강조 · 같은 길(M02 → M02)은 머리 가드가 안 물어 여기서 먼저 */
/** ☰ 메뉴 — 페이지 둘만(M01 = 시스템 관리자 · M02 = 누구나) · 열쇠 「m01」 「m02」 */
export function pageLinks(user: SettingUser, companyId: number | string | null): SettingLink[] {
const combo = settingLinks(user, companyId).find((link) => link.key === "구조물조합");
const links: SettingLink[] = [];
if (user.role === "SYSTEM_ADMIN")
links.push({
key: "m01",
title: L("B01_Dashboard_MasterElements"),
desc: "",
route: ROUTES.M01_MASTER_DATA,
});
if (combo)
links.push({ ...combo, key: "m02", title: L("M02_Title"), desc: "", group: undefined });
return links;
}
/** ☰ 진행 단계 자리 — 🏠 대시보드 + 페이지 둘 · 지금 페이지 강조(`activeKey` = 「m01」 「m02」) ·
* 같은 길(M02 → M02)은 머리 가드가 안 물어 여기서 먼저 */
export function mountSettingsDrawer(
user: SettingUser,
activeKey: string,
companyId: number | string | null,
): void {
// 계산 자리 = 대시보드 창(504-2) — 페이지가 아니라 ☰ 에 안 둠
const links = settingLinks(user, companyId).filter((link) => link.key !== "calcSites");
const bar = createStepBar(
setDrawerSteps(settingsBar(user, activeKey, companyId));
}
/** ☰ 기본 목록(772-1) — 진행 판 · 바로가기를 안 세운 로그인 뒤 쪽(B02 · D01 · E01 등) = 🏠 대시보드 + 권한별 페이지 둘 ·
* 로그인 전 = 없음 */
export async function defaultSettingsBar(): Promise<HTMLElement | null> {
const { fetchSessionUser } = await import("../A06_Login/A06_Login_Api_Fetch");
const user = await fetchSessionUser().catch(() => null);
return user ? settingsBar(user, "", null) : null;
}
function settingsBar(
user: SettingUser,
activeKey: string,
companyId: number | string | null,
): HTMLElement {
const links = pageLinks(user, companyId);
return createStepBar(
links.map((link) => link.title),
links.findIndex((link) => link.key === activeKey),
{
@@ -95,7 +141,6 @@ export function mountSettingsDrawer(
onStepClick: (index) => void go(links[index]),
},
);
setDrawerSteps(bar);
}
async function go(link: SettingLink): Promise<void> {
+8
View File
@@ -19,6 +19,7 @@ import {
releaseSelectAfterPick,
} from "@ui/ui_template_native_menu";
import { initLoupe, LOUPE_ROUTE } from "@ui/ui_template_loupe";
import { ROUTE_RESTORED_EVENT } from "@ui/ui_template_help";
import { ROUTES, type RoutePath } from "@config/config_frontend";
import { initThemeAndLang, injectShellStyles, renderShell } from "./app_shell";
import {
@@ -27,9 +28,11 @@ import {
ensureInitialHash,
setLeaveGuard,
draftRoute,
isProjectRoute,
showWithinPage,
takeLeaveGuard,
} from "./router";
import { currentProjectId, leaveProject } from "./b_project_switch";
const FOOTERLESS_ROUTES: readonly RoutePath[] = [
ROUTES.B01_ACCOUNT,
@@ -75,6 +78,10 @@ function bootstrap(): void {
"is-bm-route",
NATIVE_MENU_ROUTE.test(currentRoute()),
); // 전역 글 선택 막음(81-1)
// 프로젝트를 떠남(401) — 떠남 물음을 지난 뒤 · 그 프로젝트 세션 칸 · 3D 보관함 몫 비움
if (isProjectRoute(shown) && !isProjectRoute(currentRoute())) {
void leaveProject(currentProjectId());
}
const within = showWithinPage(currentRoute()); // B05 ↔ B06 — 보기만 바꿈(24-3a)
shown = currentRoute();
shownHash = window.location.hash;
@@ -95,6 +102,7 @@ function bootstrap(): void {
renderAll();
} else {
history.replaceState(null, "", shownHash); // 취소 — 머문 채 주소만 되돌림
window.dispatchEvent(new Event(ROUTE_RESTORED_EVENT)); // 주소 따라 그린 것(단계 [<] [>])을 다시 맞춤
}
});
});
+434
View File
@@ -0,0 +1,434 @@
/* =============================================================================
* map_guide_route.ts(logic)
* 예정노선 가이드 추천선 셈(PLAN 636-1 · 요구 546) — 기준지점을 차례로 거치는 참고용 선.
* 화면 가이드 전용 · 파이썬 짝 없음 · 저장 안 함(설계 값 아님 · 선례 `D03_PlanAnalysis_Fan.ts:8`).
* 2 m 격자 A*: 걸음 = 반지름 걸음/칸 ± 0.5 격자점(20 m = 56 방향) · |Δz| > 상한 × 걸음 길이 막음 ·
* 비용 = 길이 × (1 + 급경사 × 급경사 표본 비율 + 기울기 × (g/상한)²) + 꺾임 × 걸음 × (꺾임각/π)² ·
* 어림 = 직선 거리 × 가중(가중 A*) · 구간마다 상자 = 두 점 + 여유(못 닿으면 두 배 한 번) · 펼침 상한.
* 첫 걸음 = 정확한 시작점에서 · 끝 걸음도 기울기 검사 · 꼭짓점 그대로(20 m 다시 나누기 금지).
* 1 m 격자 = 2 칸 솎아 2 m · 급경사 = 격자 높이에서 바로 셈 · 못 닿음 = 가장 가까이 간 데까지.
* 근거 = `docs/raw/요구_1101/_분석_546-C_추천노선_라이다.md` C1 · DOM · fetch · three 없음.
* ========================================================================== */
import {
heightAt as gridHeightAt,
type DtmGrid,
} from "../../../common_util/common_util_surface_sampler";
export type Point2 = [number, number];
export type Point3 = [number, number, number];
/** 높이 원천 — heightAt(NaN = 밖 · 빈칸) · 원천 격자 칸(m) · 격자 원점(칸 꼭짓점 맞춤). */
export interface GuideTerrain {
heightAt: (x: number, y: number) => number;
/** 원천 격자 칸 m(기본 2) — 2 m 보다 잘면 솎아 2 m 안팎으로. */
cell?: number;
origin?: Point2;
}
export interface GuideWeights {
/** 급경사 표본 비율 벌점(기본 3) */
steep: number;
/** (g/상한)² 벌점(기본 0.3) */
grade: number;
/** 꺾임 벌점(기본 1) */
turn: number;
/** 어림 가중(기본 3 · 1 = 정확 A*) */
heuristic: number;
}
export interface GuideOptions {
/** 걸음 m(기본 20 · 그리기 「길이·기울기 고정」 칸) */
step?: number;
/** 기울기 상한 비율(기본 0.14) */
maxGrade?: number;
/** 급경사 문턱 %(기본 70 = 35° · 급경사 겹 둘째 경계) */
steepPct?: number;
weights?: Partial<GuideWeights>;
/** 구간마다 펼침 상한(기본 30 만 ≈ 0.4 s · 못 닿음 ≤ 0.5 s) */
maxExpand?: number;
/** 구간마다 시간 상한 ms(기본 450 · 못 닿음 ≤ 0.5 s · 느린 기계 막음) */
maxMs?: number;
}
export interface GuideSegment {
/** 꼭짓점 [x, y, z] — 닿음이면 끝 = 다음 지점 · 아니면 가장 가까이 간 데까지 */
vertices: Point3[];
reached: boolean;
/** 평면 길이 m */
length: number;
/** 걸음 가운데 가장 큰 |기울기| 비율 */
maxGrade: number;
/** 급경사(문턱 이상) 지나는 길이 비율 0~1 */
steepRatio: number;
expanded: number;
}
export interface GuideResult {
segments: GuideSegment[];
ms: number;
}
export const GUIDE_DEFAULTS = {
step: 20,
maxGrade: 0.14,
steepPct: 70,
weights: { steep: 3, grade: 0.3, turn: 1, heuristic: 3 } as GuideWeights,
maxExpand: 300_000,
maxMs: 450,
};
const MARGIN_MIN = 200;
const MARGIN_MAX = 800;
const STEEP_SAMPLES = [0.25, 0.5, 0.75, 1];
/** 서버 지형 격자(`parseDtmGrid`) → 가이드 높이 원천(원천 칸 · 원점 그대로). */
export function guideTerrainFromGrid(grid: DtmGrid): GuideTerrain {
const n = grid.x.length;
const cell = n > 1 ? (grid.x[n - 1] - grid.x[0]) / (n - 1) : 2;
return {
heightAt: (x, y) => gridHeightAt(grid, x, y),
cell,
origin: [grid.x[0], grid.y[0]],
};
}
/** 지점 차례로 잇는 가이드 선 — 지점 사이 구간마다 A*. */
export function guideRoute(
points: Point2[],
terrain: GuideTerrain,
options: GuideOptions = {},
): GuideResult {
const started = performance.now();
const segments: GuideSegment[] = [];
for (let index = 0; index + 1 < points.length; index++) {
segments.push(guideSegment(points[index], points[index + 1], terrain, options));
}
return { segments, ms: performance.now() - started };
}
/** 두 점 사이 가이드 선 하나. */
export function guideSegment(
start: Point2,
goal: Point2,
terrain: GuideTerrain,
options: GuideOptions = {},
): GuideSegment {
const step = options.step ?? GUIDE_DEFAULTS.step;
const gmax = options.maxGrade ?? GUIDE_DEFAULTS.maxGrade;
const weights = { ...GUIDE_DEFAULTS.weights, ...options.weights };
const steepGrade = (options.steepPct ?? GUIDE_DEFAULTS.steepPct) / 100;
const source = terrain.cell ?? 2;
const cell = source * Math.max(1, Math.round(2 / source));
const origin = terrain.origin ?? [0, 0];
const za = terrain.heightAt(start[0], start[1]);
const zb = terrain.heightAt(goal[0], goal[1]);
if (!Number.isFinite(za) || !Number.isFinite(zb)) {
return measure([[start[0], start[1], za]], false, 0, null);
}
const span = Math.hypot(goal[0] - start[0], goal[1] - start[1]);
const margin = Math.min(MARGIN_MAX, Math.max(MARGIN_MIN, span / 2));
const limit = options.maxExpand ?? GUIDE_DEFAULTS.maxExpand;
let spent = 0; // 두 상자 합친 펼침 수
let late = false;
const deadline = performance.now() + (options.maxMs ?? GUIDE_DEFAULTS.maxMs);
let edge = false; // 상자 끝에 닿음 = 넓히면 길이 있을 수 있음
let found = search(margin);
if (!found.reached && edge && spent < limit && !late) found = search(margin * 2);
found.expanded = spent;
return found;
function search(pad: number): GuideSegment {
const i0 = Math.floor((Math.min(start[0], goal[0]) - pad - origin[0]) / cell);
const j0 = Math.floor((Math.min(start[1], goal[1]) - pad - origin[1]) / cell);
const w = Math.ceil((Math.max(start[0], goal[0]) + pad - origin[0]) / cell) - i0 + 1;
const h = Math.ceil((Math.max(start[1], goal[1]) + pad - origin[1]) / cell) - j0 + 1;
const n = w * h;
const START = n;
const GOAL = n + 1;
const zs = new Float32Array(n).fill(Infinity); // Infinity = 아직 안 읽음 · NaN = 밖
const steepState = new Uint8Array(n); // 0 모름 · 1 급경사 · 2 아님
const g = new Float64Array(n + 2).fill(Infinity);
const parent = new Int32Array(n + 2).fill(-1);
const dir = new Float32Array(n + 2).fill(NaN);
const closed = new Uint8Array(n + 2);
const heap = new MinHeap();
const nx = (id: number) => origin[0] + (i0 + (id % w)) * cell;
const ny = (id: number) => origin[1] + (j0 + Math.floor(id / w)) * cell;
const zAt = (id: number): number => {
let value = zs[id];
if (value === Infinity) {
value = terrain.heightAt(nx(id), ny(id));
zs[id] = Number.isFinite(value) ? value : NaN;
}
return value;
};
const steepSq = (2 * cell * steepGrade) ** 2;
const steepAt = (id: number): boolean => {
if (steepState[id] === 0) {
const i = id % w;
const j = (id - i) / w;
if (i < 1 || j < 1 || i >= w - 1 || j >= h - 1) steepState[id] = 2;
else {
const gx = zAt(id + 1) - zAt(id - 1);
const gy = zAt(id + w) - zAt(id - w);
steepState[id] = gx * gx + gy * gy >= steepSq ? 1 : 2; // NaN = 아님
}
}
return steepState[id] === 1;
};
const radius = step / cell;
// 걸음 표 — 격자 차 · 길이 · 방위 · 급경사 표본 차(1/4 · 1/2 · 3/4 · 끝)
const offI: number[] = [];
const offJ: number[] = [];
const offLen: number[] = [];
const offDir: number[] = [];
const sampleD: number[] = []; // 칸 번호 차
const reach = Math.ceil(radius + 0.5);
for (let dj = -reach; dj <= reach; dj++) {
for (let di = -reach; di <= reach; di++) {
if (Math.abs(Math.hypot(di, dj) - radius) > 0.5) continue;
offI.push(di);
offJ.push(dj);
offLen.push(Math.hypot(di, dj) * cell);
offDir.push(Math.atan2(dj, di));
for (const t of STEEP_SAMPLES) {
sampleD.push(Math.round(dj * t) * w + Math.round(di * t));
}
}
}
const steepShare = weights.steep / STEEP_SAMPLES.length;
const turnWeight = (weights.turn * step) / (Math.PI * Math.PI);
// 걸음 비용 — 막히면 Infinity
const cost = (
ax: number,
ay: number,
az: number,
bx: number,
by: number,
bz: number,
inDir: number,
): number => {
const len = Math.hypot(bx - ax, by - ay);
const dz = Math.abs(bz - az);
if (!(dz <= gmax * len)) return Infinity;
let hits = 0;
for (const t of STEEP_SAMPLES) {
const si = Math.round((ax + (bx - ax) * t - origin[0]) / cell) - i0;
const sj = Math.round((ay + (by - ay) * t - origin[1]) / cell) - j0;
if (si >= 0 && sj >= 0 && si < w && sj < h && steepAt(sj * w + si)) hits++;
}
const grade = len > 0 ? dz / len / gmax : 0;
let value = len * (1 + steepShare * hits + weights.grade * grade * grade);
if (Number.isFinite(inDir) && len > 0) {
let turn = Math.abs(Math.atan2(by - ay, bx - ax) - inDir);
if (turn > Math.PI) turn = 2 * Math.PI - turn;
value += turnWeight * turn * turn;
}
return value;
};
const relax = (
from: number,
to: number,
c: number,
ax: number,
ay: number,
bx: number,
by: number,
) => {
if (!Number.isFinite(c) || g[from] + c >= g[to]) return;
g[to] = g[from] + c;
parent[to] = from;
dir[to] = Math.atan2(by - ay, bx - ax);
heap.push(g[to] + weights.heuristic * Math.hypot(goal[0] - bx, goal[1] - by), to);
};
// 끝 걸음 — 한 걸음 안이면 정확한 끝점으로(기울기 검사)
const tryGoal = (from: number, ax: number, ay: number, az: number) => {
if (Math.hypot(goal[0] - ax, goal[1] - ay) <= step + 0.5 * cell) {
relax(
from,
GOAL,
cost(ax, ay, az, goal[0], goal[1], zb, dir[from]),
ax,
ay,
goal[0],
goal[1],
);
}
};
g[START] = 0;
tryGoal(START, start[0], start[1], za);
// 첫 걸음 — 정확한 시작점에서 반지름 걸음 고리
const ci = Math.round((start[0] - origin[0]) / cell) - i0;
const cj = Math.round((start[1] - origin[1]) / cell) - j0;
for (let j = cj - reach - 1; j <= cj + reach + 1; j++) {
for (let i = ci - reach - 1; i <= ci + reach + 1; i++) {
if (i < 0 || j < 0 || i >= w || j >= h) continue;
const id = j * w + i;
const bx = nx(id);
const by = ny(id);
if (Math.abs(Math.hypot(bx - start[0], by - start[1]) - step) > 0.5 * cell) continue;
const bz = zAt(id);
if (Number.isNaN(bz)) continue;
relax(START, id, cost(start[0], start[1], za, bx, by, bz, NaN), start[0], start[1], bx, by);
}
}
let best = START;
let bestDist = span;
while (heap.size > 0 && spent < limit && !late) {
const u = heap.pop();
if (closed[u]) continue;
closed[u] = 1;
if (u === GOAL) return finish(GOAL, true);
spent++;
if ((spent & 1023) === 0 && performance.now() > deadline) late = true;
const ui = u % w;
const uj = Math.floor(u / w);
const ux = nx(u);
const uy = ny(u);
const uz = zs[u];
const left = Math.hypot(goal[0] - ux, goal[1] - uy);
if (left < bestDist) {
bestDist = left;
best = u;
}
tryGoal(u, ux, uy, uz);
const gu = g[u];
const du = dir[u];
for (let k = 0; k < offI.length; k++) {
const vi = ui + offI[k];
const vj = uj + offJ[k];
if (vi < 0 || vj < 0 || vi >= w || vj >= h) {
edge = true;
continue;
}
const v = vj * w + vi;
if (closed[v]) continue;
let vz = zs[v];
if (vz === Infinity) vz = zAt(v);
if (vz !== vz) continue; // NaN = 밖
const len = offLen[k];
const dz = Math.abs(vz - uz);
if (dz > gmax * len) continue;
let hits = 0;
for (let s = 4 * k; s < 4 * k + 4; s++) {
const at = u + sampleD[s];
const state = steepState[at];
if (state === 1 || (state === 0 && steepAt(at))) hits++;
}
const grade = dz / len / gmax;
let c = len * (1 + steepShare * hits + weights.grade * grade * grade);
if (du === du) {
let turn = Math.abs(offDir[k] - du);
if (turn > Math.PI) turn = 2 * Math.PI - turn;
c += turnWeight * turn * turn;
}
const next = gu + c;
if (next >= g[v]) continue;
g[v] = next;
parent[v] = u;
dir[v] = offDir[k];
const ex = goal[0] - (ux + offI[k] * cell);
const ey = goal[1] - (uy + offJ[k] * cell);
heap.push(next + weights.heuristic * Math.sqrt(ex * ex + ey * ey), v);
}
}
return finish(best, false);
function finish(last: number, reached: boolean): GuideSegment {
const chain: Point3[] = [];
for (let id = last; id !== -1; id = parent[id]) {
if (id === START) chain.push([start[0], start[1], za]);
else if (id === GOAL) chain.push([goal[0], goal[1], zb]);
else chain.push([nx(id), ny(id), zs[id]]);
}
chain.reverse();
return measure(chain, reached, 0, (x, y) => {
const si = Math.round((x - origin[0]) / cell) - i0;
const sj = Math.round((y - origin[1]) / cell) - j0;
return si >= 0 && sj >= 0 && si < w && sj < h && steepAt(sj * w + si);
});
}
}
// 길이 · 최대 기울기 · 급경사 지남 비율(걸음마다 칸 간격 표본)
function measure(
vertices: Point3[],
reached: boolean,
expanded: number,
steepAt: ((x: number, y: number) => boolean) | null,
): GuideSegment {
let length = 0;
let maxGrade = 0;
let steepLength = 0;
for (let k = 1; k < vertices.length; k++) {
const [ax, ay, az] = vertices[k - 1];
const [bx, by, bz] = vertices[k];
const len = Math.hypot(bx - ax, by - ay);
if (len <= 0) continue;
length += len;
maxGrade = Math.max(maxGrade, Math.abs(bz - az) / len);
if (!steepAt) continue;
const parts = Math.max(1, Math.ceil(len / cell));
for (let p = 0; p < parts; p++) {
const t = (p + 0.5) / parts;
if (steepAt(ax + (bx - ax) * t, ay + (by - ay) * t)) steepLength += len / parts;
}
}
return {
vertices,
reached,
length,
maxGrade,
steepRatio: length > 0 ? steepLength / length : 0,
expanded,
};
}
}
/** 이진 힙(값 작은 것 먼저) — 타입 배열 · 늘어남. */
class MinHeap {
private keys = new Float64Array(1024);
private ids = new Int32Array(1024);
size = 0;
push(key: number, id: number): void {
if (this.size === this.keys.length) {
const keys = new Float64Array(this.size * 2);
keys.set(this.keys);
const ids = new Int32Array(this.size * 2);
ids.set(this.ids);
this.keys = keys;
this.ids = ids;
}
let at = this.size++;
while (at > 0) {
const up = (at - 1) >> 1;
if (this.keys[up] <= key) break;
this.keys[at] = this.keys[up];
this.ids[at] = this.ids[up];
at = up;
}
this.keys[at] = key;
this.ids[at] = id;
}
pop(): number {
const top = this.ids[0];
const key = this.keys[--this.size];
const id = this.ids[this.size];
let at = 0;
for (;;) {
let child = 2 * at + 1;
if (child >= this.size) break;
if (child + 1 < this.size && this.keys[child + 1] < this.keys[child]) child++;
if (this.keys[child] >= key) break;
this.keys[at] = this.keys[child];
this.ids[at] = this.ids[child];
at = child;
}
this.keys[at] = key;
this.ids[at] = id;
return top;
}
}
+170
View File
@@ -0,0 +1,170 @@
/* =============================================================================
* map_slope_bands.ts(logic)
* 공용 지도 급경사 범위(624-3 공용으로 옮김)(PLAN 603 · 요구 466 · 467 · 468) — 기울기 % 범위 · 색 · 비율 셈(화면 · 서버 없이).
* 서버 값 그림 칸 = 기울기 % 내림(0~250 · 250 = 250 % 이상 · `D03_PlanAnalysis_Layers.slope_values_png`) ·
* 칸 v 는 가운데(v + 0.5)가 [아래, 위) 안이면 그 범위(위 = 250 이면 250 % 이상도) · 앞 범위 먼저.
* 기본 = 지식DB 15 · 35 · 40° 의 % — `임도망_편성.md:20` · `타당성평가.md:59` · `노선_배치.md:29`.
* 수평 바(631) = 경계 한 줄 ↔ 범위 · 칸 나눔 · 합침 · 경계 검사.
* 부르는 곳 = `map_slope_bands.ts` · `map_legend.ts` · D03 `UI_LayerLegend`.
* ========================================================================== */
export interface SlopeBand {
/** 아래 % (포함) */
low: number;
/** 위 % (뺌 · 250 = 끝까지) */
high: number;
/** 그림 색 이름(`--draw-map-*`) — `SLOPE_COLORS` 중 하나 */
color: string;
}
export const SLOPE_MAX_PCT = 250;
export const SLOPE_EMPTY = 255;
export const SLOPE_MAX_BANDS = 8;
/** 고를 색(이름 · 그림 색 값). */
export const SLOPE_COLORS: Array<{ name: string; color: string }> = [
{ name: "노랑", color: "--draw-map-landslide-3" },
{ name: "주황", color: "--draw-map-landslide-2" },
{ name: "빨강", color: "--draw-map-landslide-1" },
{ name: "연두", color: "--draw-map-landslide-4" },
{ name: "초록", color: "--draw-map-landslide-5" },
{ name: "하늘", color: "--draw-map-stream" },
{ name: "파랑", color: "--draw-map-zone-water" },
{ name: "보라", color: "--draw-map-zone-heritage" },
{ name: "분홍", color: "--draw-map-parcel" },
];
export const DEFAULT_SLOPE_BANDS: SlopeBand[] = [
{ low: 26.8, high: 70.0, color: "--draw-map-landslide-3" },
{ low: 70.0, high: 83.9, color: "--draw-map-landslide-2" },
{ low: 83.9, high: SLOPE_MAX_PCT, color: "--draw-map-landslide-1" },
];
export const pctToDeg = (pct: number): number => (Math.atan(pct / 100) * 180) / Math.PI;
/** 범위 고칠 거리 — 없으면 null(빈 값 · 0~250 밖 · 아래 ≥ 위 · 겹침 · 없음 · 너무 많음). */
export function checkBands(bands: SlopeBand[]): string | null {
if (!bands.length) return "범위가 하나는 있어야 합니다";
if (bands.length > SLOPE_MAX_BANDS) return `범위는 ${SLOPE_MAX_BANDS}개까지입니다`;
for (const [i, band] of bands.entries()) {
const at = `${i + 1}번째 범위`;
if (!Number.isFinite(band.low) || !Number.isFinite(band.high)) return `${at} — 값이 비었습니다`;
if (band.low < 0 || band.high > SLOPE_MAX_PCT) return `${at} — 0~${SLOPE_MAX_PCT} % 안으로`;
if (band.low >= band.high) return `${at} — 아래가 위보다 작아야 합니다`;
if (!SLOPE_COLORS.some((c) => c.color === band.color)) return `${at} — 색을 고르세요`;
}
const sorted = [...bands].sort((a, b) => a.low - b.low);
for (let i = 1; i < sorted.length; i++)
if (sorted[i].low < sorted[i - 1].high)
return `범위가 겹칩니다 — ${sorted[i - 1].low}~${sorted[i - 1].high} % 와 ${sorted[i].low}~${sorted[i].high} %`;
return null;
}
/** 저장 글 → 범위(깨졌거나 맞지 않으면 기본). */
export function parseBands(raw: string | null): SlopeBand[] {
try {
const got = JSON.parse(raw ?? "null") as unknown;
if (Array.isArray(got)) {
const bands = got.map((b: Partial<SlopeBand>) => ({
low: Number(b?.low),
high: Number(b?.high),
color: String(b?.color),
}));
if (!checkBands(bands)) return bands;
}
} catch {
// 깨진 값 = 기본
}
return DEFAULT_SLOPE_BANDS.map((b) => ({ ...b }));
}
/** 칸 값 v(0~255) → 범위 차례(없으면 −1). */
export function bandOf(v: number, bands: SlopeBand[]): number {
if (v > SLOPE_MAX_PCT) return -1;
const mid = v + 0.5;
return bands.findIndex(
(b) => b.low <= mid && (mid < b.high || (b.high >= SLOPE_MAX_PCT && v === SLOPE_MAX_PCT)),
);
}
/** 1 % 칸 수 → 범위별 비율(유효 칸 대비). */
export function bandRatios(counts: number[], bands: SlopeBand[]): number[] {
const total = counts.reduce((sum, n) => sum + n, 0) || 1;
const out = bands.map(() => 0);
counts.forEach((n, v) => {
const i = bandOf(v, bands);
if (i >= 0) out[i] += n;
});
return out.map((n) => n / total);
}
/** 수평 바 꼴(631 · 요구 537) — 경계 = [0, …, 250] 한 줄 · 칸 i = [경계 i, 경계 i+1) · 색 빈 글 = 칠 안 함. */
export interface SlopeBar {
edges: number[];
colors: string[];
}
/** 범위 → 바(사이 빈 곳 = 칠 안 함 칸). */
export function bandsToBar(bands: SlopeBand[]): SlopeBar {
const edges = [0];
const colors: string[] = [];
for (const band of [...bands].sort((a, b) => a.low - b.low)) {
if (band.low > edges.at(-1)!) {
edges.push(band.low);
colors.push("");
}
edges.push(band.high);
colors.push(band.color);
}
if (edges.at(-1)! < SLOPE_MAX_PCT) {
edges.push(SLOPE_MAX_PCT);
colors.push("");
}
return { edges, colors };
}
/** 바 → 범위(칠 안 함 칸은 뺌). */
export const barToBands = (bar: SlopeBar): SlopeBand[] =>
bar.colors.flatMap((color, i) =>
color ? [{ low: bar.edges[i], high: bar.edges[i + 1], color }] : [],
);
/** 경계 고칠 거리 — 없으면 null(빈 값 · 0 < 값 < 250 밖 · 같은 값 · 거꾸로). */
export function checkEdges(edges: number[]): string | null {
for (let i = 1; i < edges.length - 1; i++) {
const at = `${i}번째 경계`;
if (!Number.isFinite(edges[i])) return `${at} — 값이 비었습니다`;
if (edges[i] <= 0 || edges[i] >= SLOPE_MAX_PCT)
return `${at} — 0 보다 크고 ${SLOPE_MAX_PCT} % 보다 작게`;
if (edges[i] === edges[i - 1]) return `${at} — 옆 경계와 같은 값입니다`;
if (edges[i] < edges[i - 1]) return `${at} — 왼쪽 경계보다 커야 합니다`;
}
return null;
}
const colored = (bar: SlopeBar): number => bar.colors.filter(Boolean).length;
/** 칸 i 를 반으로(위 반 = 새 색) — 못 나누면 null(색 칸 SLOPE_MAX_BANDS · 너무 좁음 · 경계 틀림). */
export function splitBar(bar: SlopeBar, i: number): SlopeBar | null {
if (colored(bar) >= SLOPE_MAX_BANDS || checkEdges(bar.edges)) return null;
const [low, high] = [bar.edges[i], bar.edges[i + 1]];
const mid = Math.round((low + high) * 5) / 10;
if (!(mid > low && mid < high)) return null;
const color =
SLOPE_COLORS.find((c) => !bar.colors.includes(c.color))?.color ??
SLOPE_COLORS[colored(bar) % SLOPE_COLORS.length].color;
return {
edges: [...bar.edges.slice(0, i + 1), mid, ...bar.edges.slice(i + 1)],
colors: [...bar.colors.slice(0, i + 1), color, ...bar.colors.slice(i + 1)],
};
}
/** 칸 i 를 이웃과 합침(왼쪽 · 첫 칸은 오른쪽 · 이웃 색 남음) — 칸 하나면 null. */
export function mergeBar(bar: SlopeBar, i: number): SlopeBar | null {
if (bar.colors.length <= 1) return null;
const cut = i > 0 ? i : 1;
return {
edges: bar.edges.filter((_, k) => k !== cut),
colors: bar.colors.filter((_, k) => k !== i),
};
}
+28
View File
@@ -0,0 +1,28 @@
/* 공용 긋기 방식 칸(626-3 · `map_draw_kind.ts`) — 고름 아래 길이 · 기울기 두 열 */
.map-draw-kind {
display: flex;
flex-direction: column;
gap: 6px;
}
.map-draw-kind__fields {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--spacing-8);
align-items: start;
}
.map-draw-kind__fields[hidden] {
display: none;
}
.map-draw-kind__fields .ui-field {
margin: 0;
min-width: 0;
}
.map-draw-kind__fields .ui-input-unit,
.map-draw-kind__fields .ui-input {
width: 100%;
min-width: 0;
}
+161
View File
@@ -0,0 +1,161 @@
/* =============================================================================
* map_draw_kind.ts
* 공용 긋기 방식 칸(PLAN 626-3 · 599-1 · 625-1) — 예정노선(D03) 그리기 칸 · 노드 판 · B05 평면 예정노선 그리기가 같이 씀.
* 꼴 = 마디 고름 2 × 2(자유지정 · 길이·기울기 고정 / 길이 고정 · 기울기 고정) + 길이 · 기울기 두 열(기울기 아래 ≈ °) ·
* 방식에 안 쓰는 칸은 숨김(막힘 = 권한 없음만) · 설명 글 없음(요구 463-6).
* 쓰는 법 = `createDrawKindFields({ value, onChange })` → { root, get, set, setDisabled, setActive }.
* 켬끔 꼴(589-9) = `onActive` 를 주면 고른 방식을 다시 누르면 풀림 · 방식을 고르면 켜짐 — 켬 = 긋기 시작 · 끔 = 긋기 끝
* (지금 켬인지는 `setActive` 로 맞춤 · 노드 판처럼 `onActive` 없는 곳은 늘 하나 고름).
* ========================================================================== */
import type { UiSize } from "@ui/ui_template_button";
import { createInputField } from "@ui/ui_template_input";
import { createSegment } from "@ui/ui_template_segment";
import { createText } from "@ui/ui_template_text";
import "./map_draw_kind.css";
/** 기울기 % → 각(°) 한 자리 글. */
const slopeDegText1 = (pct: number): string => ((Math.atan(pct / 100) * 180) / Math.PI).toFixed(1);
/** 제약 칸 처음 값 — 길이 = 측점 간격(지식DB 설계제원_총괄) · 기울기 = 간선 20 km/h 일반지형 상한(종단선형). */
export const DRAW_DEFAULT_LENGTH_M = 20;
export const DRAW_DEFAULT_SLOPE_PCT = 9;
export type DrawConstraint = "free" | "length" | "slope" | "both";
export interface DrawKindValue {
kind: DrawConstraint;
lengthM: number;
slopePct: number;
}
export const DRAW_KIND_LABELS: Record<DrawConstraint, string> = {
free: "자유지정",
both: "길이·기울기 고정",
length: "길이 고정",
slope: "기울기 고정",
};
export interface DrawKindFields {
root: HTMLElement;
get: () => DrawKindValue;
/** 값을 바꿈(onChange 는 안 부름). */
set: (value: Partial<DrawKindValue>) => void;
setDisabled: (disabled: boolean) => void;
/** 켬끔 꼴에서 지금 켬인지 맞춤(onActive 는 안 부름 · 끔 = 모두 풀린 모양). */
setActive: (on: boolean) => void;
}
/** 긋기 방식 칸 — 고름 · 길이 · 기울기가 바뀔 때마다 `onChange(지금 값)`(빈 · 틀린 수는 칸 오류만). */
export function createDrawKindFields(opts: {
value: DrawKindValue;
onChange: (value: DrawKindValue) => void;
/** 켬끔 꼴 — 방식 고름 = true · 고른 방식 다시 누름 = false. */
onActive?: (on: boolean) => void;
size?: UiSize;
}): DrawKindFields {
const size = opts.size ?? 24;
let value = { ...opts.value };
let disabled = false;
let active = !opts.onActive;
const changed = (): void => opts.onChange({ ...value });
const segment = createSegment({
options: (Object.keys(DRAW_KIND_LABELS) as DrawConstraint[]).map((kind) => ({
value: kind,
label: DRAW_KIND_LABELS[kind],
})),
value: active ? value.kind : "",
size,
columns: 2,
clearable: !!opts.onActive,
ariaLabel: "긋기 방식",
onChange: (next) => {
if (next === "") return void ((active = false), opts.onActive?.(false));
value.kind = next as DrawConstraint;
fieldsState();
changed();
if (opts.onActive && !active) ((active = true), opts.onActive(true));
},
});
segment.root.dataset.role = "draw-constraint";
const lengthField = createInputField({
label: "길이",
type: "number",
value: String(value.lengthM),
min: 1,
unit: "m",
size,
onInput: (text) => {
const next = Number(text);
lengthField.setError(next > 0 ? undefined : "0 보다 큰 수");
if (!(next > 0)) return;
value.lengthM = next;
changed();
},
});
lengthField.input.dataset.role = "draw-length";
const slopeField = createInputField({
label: "기울기",
type: "number",
value: String(value.slopePct),
unit: "%",
size,
onInput: (text) => {
const next = Number(text);
const ok = text.trim() !== "" && Number.isFinite(next);
slopeField.setError(ok ? undefined : "숫자");
if (!ok) return;
value.slopePct = next;
degText();
changed();
},
});
slopeField.input.dataset.role = "draw-slope";
const slopeDegText = createText("", "muted");
slopeDegText.dataset.role = "draw-slope-deg";
const degText = (): void => {
slopeDegText.textContent = `≈ ${slopeDegText1(Math.abs(value.slopePct))}°`;
};
const slopeColumn = document.createElement("div");
slopeColumn.append(slopeField.root, slopeDegText);
const fields = document.createElement("div");
fields.className = "map-draw-kind__fields";
fields.append(lengthField.root, slopeColumn);
const root = document.createElement("div");
root.className = "map-draw-kind";
root.append(segment.root, fields);
function fieldsState(): void {
for (const button of segment.root.querySelectorAll("button")) button.disabled = disabled;
lengthField.input.disabled = slopeField.input.disabled = disabled;
fields.hidden = value.kind === "free";
lengthField.root.hidden = value.kind === "slope";
slopeColumn.hidden = value.kind === "length";
}
degText();
fieldsState();
return {
root,
get: () => ({ ...value }),
set(next) {
value = { ...value, ...next };
segment.set(active ? value.kind : "");
lengthField.input.value = String(value.lengthM);
slopeField.input.value = String(value.slopePct);
lengthField.setError(undefined);
slopeField.setError(undefined);
degText();
fieldsState();
},
setDisabled(next) {
disabled = next;
fieldsState();
},
setActive(on) {
if (!opts.onActive) return;
active = on;
segment.set(active ? value.kind : "");
},
};
}
+235
View File
@@ -0,0 +1,235 @@
/* =============================================================================
* map_forest_layers.ts
* 공용 지도 — 받은 산림 · 환경 자료 겹(PLAN 622-2 · 624-2 · 요구 529 · 530) — 서버 줌별 조각(622-1)을 GL 고정 소스로(위성 · 등고선 615 꼴).
* 벡터 = 임도망도(forest_road · 전국 국가임도 · 간선 · 작업 · 산불 색 · 줌 따라 굵기 · 줌 14 부터 연도 · 종류 글) · 산림입지도(forest_soil · 토양 무리 색 ·
* 반투명 · z10 아래 숨김) · 경제림(eco_forest · 국유 · 사유 테두리) /
* 그림 = 생태자연도(ecology) · 산사태위험지도(landslide · 옛 캔버스는 받은 범위 한 장 그대로) · 암상 지질도(geology_map · 760-1 · 도형 = `map_geology`).
* 조각만 쓰는 겹(`TILE_ONLY`) = 받기 없음 · 옛 캔버스(그래픽 카드 끔)엔 안 보임.
* 토양 무리 색(`soilColor`) = 조각(`code` = SLTP_CD 두 자리) · 브이월드 요약(기호 「B₁」) 같은 색 — 범례 · 옛 캔버스도 이것.
* 부르는 곳 = `map_layer_specs`(꼴) · D03 `UI_MapGl`(소스) · `UI_Layers`(받기 거름) · `map_legend`(범례).
* ========================================================================== */
import type { ExpressionSpecification, SourceSpecification } from "maplibre-gl";
import type { LegendItem } from "@ui/ui_template_flow_legend";
import { GL_FONT, tileSource, type GlSpec } from "@ui/ui_template_gl_map";
import { themeColor } from "@ui/ui_template_palette";
import type { MapLayerKey } from "./map_layer_list";
/** 조각만 쓰는 겹 — 받을 자료 없음(위성 포함). */
const TILE_ONLY = ["satellite", "nationalRoad", "ecoForest", "ecology"] as const;
export type TileOnlyKey = (typeof TILE_ONLY)[number];
export const tileOnly = (key: MapLayerKey): key is TileOnlyKey =>
(TILE_ONLY as readonly string[]).includes(key);
/** 받는 동안 서클에 세는 조각 소스(등고선 포함). */
export const TRACKED_SOURCES = [
"contour",
"forest_road",
"forest_soil",
"eco_forest",
"ecology",
"landslide",
"geology_map",
];
/** 고정 소스 — 벡터 z8~15(서버 줌별 단순화) · 그림 z6~19. */
export function forestSources(projectId: string): Record<string, SourceSpecification> {
const vt = (name: string) => tileSource(projectId, `vt/${name}`, "pbf", 8, 15);
const wmt = (name: string) => tileSource(projectId, `wmt/${name}`, "png", 6, 19);
return {
forest_road: vt("forest_road"),
forest_soil: vt("forest_soil"),
eco_forest: vt("eco_forest"),
ecology: wmt("ecology"),
landslide: wmt("landslide"),
geology_map: wmt("geology"),
};
}
/** 토양형 코드(산림입지토양도 SLTP_CD) → 기호. 무리 = 갈색 · 적황색 · 암적색 · 회갈색 · 화산회 · 침식 · 미숙 · 그 밖(비산림). */
const SOIL_SYMBOLS: Record<string, string> = {
"01": "B1", "02": "B2", "03": "B3", "04": "B4",
"05": "rB1", "06": "rB2", "07": "R1", "08": "R2", "09": "Y",
"10": "DR1", "11": "DR2", "12": "DR3", "13": "DRb1", "14": "DRb2",
"15": "GrB1", "16": "GrB2", "17": "Va1", "18": "Va2", "19": "Va3", "20": "Va4",
"21": "Va-R1", "22": "Va-R2", "23": "Va-gr",
"24": "Er1", "25": "Er2", "26": "Er-c", "27": "Im", "28": "Li", "29": "Bs",
}; // prettier-ignore
/** 무리 첫 코드(차례 = `--draw-map-soil-1..6`). */
const SOIL_GROUP_START = [1, 5, 10, 15, 24, 30];
const soilToken = (group: number): string => `--draw-map-soil-${group + 1}`;
const SUB = /[₀-₉]/g;
/** 토양형(조각 두 자리 코드 · 브이월드 기호 「B₁」) → 무리 색 값 · 모름 = 「그 밖」. */
export function soilColor(code: string): string {
const plain = code.trim().replace(SUB, (c) => String(c.charCodeAt(0) - 0x2080));
const found = /^\d+$/.test(plain)
? Number(plain)
: Number(Object.keys(SOIL_SYMBOLS).find((key) => SOIL_SYMBOLS[key] === plain) ?? 99);
return soilToken(Math.max(SOIL_GROUP_START.filter((start) => found >= start).length - 1, 0));
}
/** 임도 사업 구분(`kind` 1 간선 · 2 작업 · 3 산불) 색 값. */
const ROAD_KINDS = [
{ kind: 1, label: "간선", color: "--draw-map-national-road-main" },
{ kind: 2, label: "작업", color: "--draw-map-national-road-work" },
{ kind: 3, label: "산불", color: "--draw-map-national-road-fire" },
];
const ECO_KINDS = [
{ own: "국유", color: "--draw-map-eco-national" },
{ own: "사유", color: "--draw-map-eco-private" },
];
/** 생태자연도 범례 = 환경부 WMS 그림 색(3등급 = 칠 없음). */
const ECOLOGY_LEGEND = [
{ label: "1등급", color: "--draw-map-ecology-1" },
{ label: "2등급", color: "--draw-map-ecology-2" },
{ label: "별도관리지역", color: "--draw-map-ecology-x" },
];
const HALO = "rgba(0, 0, 0, 0.55)";
const ROUND = { "line-join": "round", "line-cap": "round" } as const;
const zoomWidth = (z8: number, z12: number, z15: number): ExpressionSpecification =>
["interpolate", ["linear"], ["zoom"], 8, z8, 12, z12, 15, z15] as ExpressionSpecification;
type Spec = GlSpec<MapLayerKey>;
const raster = (
key: "landslide" | "ecology" | "geology",
opacity: number,
source: string = key,
): Spec =>
({
key,
id: source === key ? key : source,
type: "raster",
source,
paint: { "raster-opacity": opacity, "raster-fade-duration": 0 },
}) as Spec;
/** 그림 겹(암상 지질도 · 산사태위험 · 생태자연도) — 아래 차례(급경사 위). */
export const forestRasterSpecs = (): Spec[] => [
raster("geology", 0.5, "geology_map"),
raster("landslide", 0.6),
raster("ecology", 0.55),
];
/** 면 겹(산림입지도 · 경제림) — 보호구역 아래. */
export function forestAreaSpecs(): Spec[] {
const soilAt = (group: number): string => themeColor(soilToken(group), "#e879f9");
const soil = [
"step",
["to-number", ["get", "code"], 99],
soilAt(0),
...SOIL_GROUP_START.slice(1).flatMap((start, i) => [start, soilAt(i + 1)]),
] as ExpressionSpecification;
const eco = [
"match",
["get", "own"],
...ECO_KINDS.flatMap(({ own, color }) => [own, themeColor(color, "#34d399")]),
themeColor(ECO_KINDS[1].color, "#a78bfa"),
] as unknown as ExpressionSpecification;
const vector = (source: string) => ({ source, "source-layer": source });
return [
{
key: "soils",
id: "soils",
type: "fill",
...vector("forest_soil"),
minzoom: 10,
paint: { "fill-color": soil, "fill-opacity": 0.35 },
} as Spec,
{
key: "soils",
id: "soils-edge",
type: "line",
...vector("forest_soil"),
minzoom: 10,
layout: ROUND,
paint: { "line-color": soil, "line-width": 0.8, "line-opacity": 0.9 },
} as Spec,
{
key: "ecoForest",
id: "ecoForest",
type: "line",
...vector("eco_forest"),
layout: ROUND,
paint: { "line-color": eco, "line-width": zoomWidth(1, 1.8, 2.4) },
} as Spec,
];
}
/** 임도망도(전국 국가임도) 선 — 검은 테 + 사업 구분 색(도로 위 · 임도(업로드) 아래). */
export function forestRoadSpecs(): Spec[] {
const color = [
"match",
["get", "kind"],
...ROAD_KINDS.flatMap(({ kind, color }) => [kind, themeColor(color, "#ff6b00")]),
themeColor(ROAD_KINDS[0].color, "#ff6b00"),
] as unknown as ExpressionSpecification;
const line = (id: string, paint: Record<string, unknown>): Spec =>
({
key: "nationalRoad",
id,
type: "line",
source: "forest_road",
"source-layer": "forest_road",
layout: ROUND,
paint,
}) as Spec;
return [
line("nationalRoad-halo", { "line-color": HALO, "line-width": zoomWidth(2.2, 3.6, 5.6) }),
line("nationalRoad", { "line-color": color, "line-width": zoomWidth(1, 2, 3.6) }),
];
}
/** 임도망도 글(요구 534) — 줌 14 부터 선 따라 「개설 연도 · 종류」(연도 0 = 종류만) · 겹치면 뺌 · 꼴 = 지명 글. */
export function forestRoadLabelSpec(): Spec {
const kind = [
"match",
["get", "kind"],
...ROAD_KINDS.flatMap(({ kind, label }) => [kind, label]),
"",
] as unknown as ExpressionSpecification;
return {
key: "nationalRoad",
id: "nationalRoad-label",
type: "symbol",
source: "forest_road",
"source-layer": "forest_road",
minzoom: 14,
layout: {
"symbol-placement": "line",
"symbol-spacing": 300,
"text-field": [
"concat",
["case", [">", ["get", "year"], 0], ["concat", ["to-string", ["get", "year"]], " · "], ""],
kind,
],
"text-font": [GL_FONT],
"text-size": 11,
"text-max-angle": 30,
"text-padding": 2,
},
paint: {
"text-color": themeColor("--draw-map-place", "#f8fafc"),
"text-halo-color": "rgba(0, 0, 0, 0.7)",
"text-halo-width": 1.5,
},
} as unknown as Spec;
}
/** 범례 — 켠 조각 겹(산사태위험 = 범례 자리 `LANDSLIDE_LEGEND` 따로). */
export function forestLegendRows(on: Record<MapLayerKey, boolean>): LegendItem[][] {
const item = (label: string, color: string, kind?: LegendItem["kind"]): LegendItem => ({
label,
color: `var(${color})`,
...(kind ? { kind } : {}),
});
const rows: LegendItem[][] = [];
if (on.nationalRoad)
rows.push(ROAD_KINDS.map(({ label, color }) => item(`임도망도 ${label}`, color, "line")));
if (on.ecoForest)
rows.push(ECO_KINDS.map(({ own, color }) => item(`경제림 ${own}`, color, "line")));
if (on.ecology)
rows.push(ECOLOGY_LEGEND.map(({ label, color }) => item(`생태자연도 ${label}`, color)));
return rows;
}
+161
View File
@@ -0,0 +1,161 @@
/* =============================================================================
* map_geology.ts
* 공용 지도 암상 지질도 겹(PLAN 760-1 · 요구 597-3 · 598-3) — D03 예정노선 · B05 평면이 같이 씀.
* 그림 = KIGAM 5만 WMS 조각(`map_forest_layers` 「geology_map」) · 도형 = 서버 `/plan/layers/geology`
* (기호별 요약 · 경위도 면 · 노선마다 암상 구간 · 키는 서버만).
* 색 = 바위 갈래(서버 `ROCK_GROUPS` 차례) → `--draw-map-geology-1..6` · 면 위 글 = 「기호 · 첫 암상 낱말」.
* 범례 줄(`geologyRow`) · 노선 구간 표(`geologyTail` · 참고 — 토사 · 암 판정 아님) = 범례 칸.
* ========================================================================== */
import type { LegendItem } from "@ui/ui_template_flow_legend";
import { GL_FONT, type GlSpec } from "@ui/ui_template_gl_map";
import { themeColor } from "@ui/ui_template_palette";
import { createTable } from "@ui/ui_template_table";
import { el } from "@ui/ui_template_elements_base";
import { stationLabel } from "@util/common_util_station";
import { request, type GeoJsonCollection } from "./map_layer_api";
import { hectare } from "./map_layer_data";
import type { MapLayerKey } from "./map_layer_list";
/** 기호 하나(받은 범위 안). */
export interface GeologyUnit {
symnum: string;
symbol: string;
litho: string;
strata: string;
age: string;
sheet: string;
group: number;
count: number;
area_m2: number;
}
/** 노선 구간 — `i` = 요약 차례 · -1 = 자료 없음. */
export interface GeologySegment {
from_m: number;
to_m: number;
i: number;
}
export interface PlanGeology extends GeoJsonCollection {
source: string;
summary: GeologyUnit[];
groups: string[];
routes: Array<{ name: string; segments: GeologySegment[] }>;
}
const GROUP_FALLBACK = ["#f472b6", "#fb923c", "#fde047", "#818cf8", "#e5e7eb", "#5eead4"];
const groupToken = (group: number): string => `--draw-map-geology-${Math.min(group, 5) + 1}`;
const firstWord = (text: string): string => text.split(/\s+/)[0] ?? "";
/** 긴 암상 글 줄임(범례 · 표 폭). */
const short = (text: string, max = 22): string =>
text.length > max ? `${text.slice(0, max)}…` : text;
/** 받기 — 면 위 글 꼴을 먼저 읽어 둠(한글 글자). */
export async function fetchPlanGeology(projectId: string): Promise<PlanGeology> {
const got = await request<PlanGeology>(`/projects/${projectId}/plan/layers/geology`);
const text = got.summary.map((unit) => unit.symbol + unit.litho).join("") || "가";
await document.fonts.load(`600 24px "${GL_FONT}"`, text).catch(() => null);
return got;
}
/** GL 자료 — 지물마다 색(`_color`) · 글(`_label`). */
export function geologyCollection(info: PlanGeology): GeoJsonCollection {
return {
features: (info.features ?? []).map((feature) => {
const unit = info.summary[Number(feature.properties?.["i"])];
const group = unit?.group ?? 5;
return {
...feature,
properties: {
...feature.properties,
_color: themeColor(groupToken(group), GROUP_FALLBACK[group] ?? GROUP_FALLBACK[5]),
_label: unit ? `${unit.symbol} · ${firstWord(unit.litho)}` : "",
},
};
}),
};
}
/** GL 꼴 — 연한 채움 · 갈래 색 테두리 · 면 위 글(겹치면 뺌). */
export function geologySpecs(): GlSpec<MapLayerKey>[] {
const color = ["get", "_color"];
return [
{
key: "geology",
id: "geology",
type: "fill",
source: "geology",
paint: { "fill-color": color, "fill-opacity": 0.2 },
},
{
key: "geology",
id: "geology-edge",
type: "line",
source: "geology",
layout: { "line-join": "round" },
paint: { "line-color": color, "line-width": 1.6, "line-opacity": 0.95 },
},
{
key: "geology",
id: "geology-label",
type: "symbol",
source: "geology",
layout: {
"text-field": ["get", "_label"],
"text-font": [GL_FONT],
"text-size": 12,
"text-padding": 4,
},
paint: {
"text-color": color,
"text-halo-color": "rgba(0, 0, 0, 0.75)",
"text-halo-width": 1.5,
},
},
] as unknown as GlSpec<MapLayerKey>[];
}
const unitColor = (unit: GeologyUnit | undefined): string =>
unit ? `var(${groupToken(unit.group)})` : "transparent";
/** 범례 줄 — 기호마다 「기호 대표암상 넓이」(갈래 색) · 없으면 한 줄. */
export function geologyRow(info: PlanGeology): LegendItem[] {
if (!info.summary.length)
return [{ label: "암상 지질도 없음(받은 범위 안)", color: "transparent", off: true }];
return info.summary.map((unit) => ({
label: `${unit.symbol} ${short(unit.litho || unit.strata)} ${hectare(unit.area_m2)}`,
color: unitColor(unit),
}));
}
/** 노선 따라 바위 종류 구간 표(노선마다) + 참고 글 — 켬 · 받음 · 노선 있을 때만. */
export function geologyTail(on: Record<MapLayerKey, boolean>, info?: PlanGeology): HTMLElement[] {
if (!on.geology || !info?.routes.length) return [];
const columns = [
{ label: "측점" },
{ label: "길이", align: "end" as const },
{ label: "기호" },
{ label: "대표암상" },
];
const tables = info.routes.map((route) => {
const rows = route.segments.map((seg) => {
const unit = info.summary[seg.i];
return [
`${stationLabel(seg.from_m, 20)} ~ ${stationLabel(seg.to_m, 20)}`,
`${(seg.to_m - seg.from_m).toFixed(0)} m`,
unit?.symbol ?? "—",
unit ? short(unit.litho || unit.strata, 30) : "자료 없음",
];
});
return el("div", {
className: "ui-legend ui-legend--vertical",
children: [`암상 구간 — ${route.name}`, createTable(columns, rows, { density: "data" })],
});
});
const note = el("div", {
className: "ui-legend",
children: ["참고 — 토사 · 암 판정 아님(1:50,000 바위 종류 · 경계 수십 m 어긋날 수 있음)"],
});
return [...tables, note];
}
+234
View File
@@ -0,0 +1,234 @@
/* =============================================================================
* map_layer_api.ts
* 공용 지도 겹 받기(PLAN 569 · 581 · 584 · 603 · 605 · 606 · 624-3 · 624-4 · 요구 530) — D03 예정노선 · B05 평면이 같이 씀(D03_Api 에서 옮김).
* 서버 길 = `/api/projects/{id}/plan/…`(624-1 · 임도 프로젝트도 · 띠 · 올린 임도망도 = 예정노선만).
* 급경사 · 음영 · 0차곡 = 지형 원천 인자 `terrain`(지형 칩 값 · 624-4).
* 지적도 · 도엽 겹 GeoJSON 경위도 · 도엽 조각 · 그림 겹(급경사 · 음영기복 · 산사태위험) · 보호구역 · 산림입지도 ·
* 선 겹(단층 · 지질구조선) · 0차곡 · 띠 · 올린 임도망도(올리기 · 지움) · 산사태취약지역.
* D03 = `D03_PlanAnalysis_Api` 가 다시 내놓음(부르는 곳 그대로).
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
import type { TerrainSource } from "./map_terrain_chip";
/** 경위도 GeoJSON 묶음 — B04 `GeoJsonCollection` 과 같은 꼴(공용 폴더는 페이지 파일을 안 부름). */
export type GeoJsonCollection = {
features?: Array<{
geometry?: { type: string; coordinates: unknown } | null;
properties?: Record<string, unknown> | null;
}>;
};
export async function request<T>(path: string): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, {
credentials: "include",
headers: { "Content-Type": "application/json" },
});
const body = (await response.json().catch(() => ({}))) as T & { detail?: unknown };
if (!response.ok) {
const detail = typeof body.detail === "string" ? body.detail : `HTTP ${response.status}`;
throw new Error(detail);
}
return body;
}
const base = (projectId: string): string => `/projects/${projectId}/plan`;
/** 지도 겹 그림(569-1) — 작업 좌표계 m 상자(칸 가장자리) · data URL(위 = 북). */
export interface PlanRaster {
box_m: [number, number, number, number];
/** 경위도 네 귀퉁이(왼쪽 아래 · 오른쪽 아래 · 오른쪽 위 · 왼쪽 위) — GL 바탕(586-1). */
box?: Array<[number, number]>;
image: string;
/** 급경사만(603) — `counts[v]` = 기울기 % 내림 v(0~250) 칸 수 · 그림 = 회색 칸 값(255 = 빈 칸). */
counts?: number[];
/** 쓴 지형 원천(624-1 · 급경사 · 음영). */
source?: string;
}
/** 지형 원천 인자(624-1) — 안 넘김 = 서버 기본(D = 수치지형도 · B = 설계 지형). */
const terrainQuery = (terrain?: TerrainSource): string => (terrain ? `&terrain=${terrain}` : "");
export const fetchPlanCadastral = (projectId: string): Promise<GeoJsonCollection> =>
request(`${base(projectId)}/layers/cadastral`);
/** 도엽 겹(`도로중심선` 등) — 없으면 서버가 받은 도엽에서 풀어 냄. */
export const fetchPlanSheetLayer = (projectId: string, key: string): Promise<GeoJsonCollection> =>
request(`${base(projectId)}/layers/sheet?key=${encodeURIComponent(key)}`);
/** 보이는 도엽(606-2) — 지도 보기 경위도 상자 → 수 · 번호(가운데부터 · 30 장 넘음 = 빈 목록). */
export const fetchPlanSheetTiles = (
projectId: string,
bbox: [number, number, number, number],
): Promise<{ count: number; sheets: string[] }> =>
request(`${base(projectId)}/sheet-tiles?bbox=${bbox.map((v) => v.toFixed(5)).join(",")}`);
/** 도엽 조각 하나(606-1) — 공용 창고 · 없으면 서버가 만듦. */
export const fetchPlanSheetTile = (
projectId: string,
sheet: string,
name: string,
): Promise<GeoJsonCollection> =>
request(`${base(projectId)}/sheet-tiles/${sheet}/${encodeURIComponent(name)}`);
/** 그림 겹 — 급경사 = PNG 바이트 + 머리 `X-Plan-Raster`(616-4 · `image` = blob 주소 · 다 읽으면
* `slopeValues` 가 놓음) · 그 밖 = JSON(base64 그림). */
export const fetchPlanRaster = async (
projectId: string,
kind: "slope" | "hillshade" | "landslide",
terrain?: TerrainSource,
): Promise<PlanRaster> => {
const path = `${base(projectId)}/layers/raster?kind=${kind}${terrainQuery(terrain)}`;
if (kind !== "slope") return request(path);
const response = await fetch(`${API_BASE_URL}${path}`, { credentials: "include" });
if (!response.ok) {
const body = (await response.json().catch(() => ({}))) as { detail?: unknown };
throw new Error(typeof body.detail === "string" ? body.detail : `HTTP ${response.status}`);
}
const meta = JSON.parse(response.headers.get("X-Plan-Raster") ?? "{}") as PlanRaster;
return { ...meta, image: URL.createObjectURL(await response.blob()) };
};
/** 보호 · 규제 구역(581-1) — 지물 `kind` · `name` · `area_m2` + 종류별 개수 · 넓이(실패 = `failed`). */
export interface PlanZones extends GeoJsonCollection {
summary: Record<string, { label: string; count: number; area_m2: number; failed?: boolean }>;
}
export const fetchPlanZones = (projectId: string): Promise<PlanZones> =>
request(`${base(projectId)}/layers/zones`);
/** 산림입지도(581-2) — 지물 `code` · `name` · `area_m2` + 토양형별 개수 · 넓이(코드 차례). */
export interface PlanSoils extends GeoJsonCollection {
summary: Array<{ code: string; name: string; count: number; area_m2: number }>;
}
export const fetchPlanSoils = (projectId: string): Promise<PlanSoils> =>
request(`${base(projectId)}/layers/soils`);
/** 계류 100 m · 도로 300 m 띠(581-1) — 받은 범위 안 넓이 · 비율. */
export interface PlanBand {
dist_m: number;
lines: number;
area_m2: number;
ratio: number;
collection: GeoJsonCollection;
}
/** 선 겹(584-1) — 받은 범위 안 선 + 가닥 · 길이 m. */
export interface PlanLines extends GeoJsonCollection {
summary: { count: number; length_m: number };
}
/** 단층 · 지질구조선(브이월드 WFS · 서버 `LINE_KINDS` 열쇠). */
export const fetchPlanLines = (projectId: string, kind: string): Promise<PlanLines> =>
request(`${base(projectId)}/layers/lines?kind=${encodeURIComponent(kind)}`);
/** 0차곡(584-1 · 지형 추정) — 위 집수 넓이 [min_m2, max_m2) 선 + 가닥 · 길이 m. */
export interface PlanZeroOrder {
min_m2: number;
max_m2: number;
cell_m: number;
count: number;
length_m: number;
collection: GeoJsonCollection;
/** 쓴 지형 원천(624-1). */
source?: string;
}
export const fetchPlanZeroOrder = (
projectId: string,
terrain?: TerrainSource,
): Promise<PlanZeroOrder> =>
request(`${base(projectId)}/layers/zero-order?${terrainQuery(terrain).slice(1)}`);
export const fetchPlanBands = (projectId: string): Promise<{ stream: PlanBand; road: PlanBand }> =>
request(`${base(projectId)}/layers/bands`);
/** 올린 묶음 한 줄(605-1) — 올릴 때마다 하나 · 자른 가닥 · 길이 m · 원본 선 수 · 잘린 상자 · 기준점 거리. */
export interface PlanRoadBundle {
id: string;
name: string;
files: string[];
bytes: number | null;
uploaded_at: string | null;
count: number;
length_m: number;
total: number;
bounds: [number, number, number, number] | null;
distance_m: number | null;
}
/** 올린 임도망도(584-3 · 595-4 · 605-1) — 묶음 모두를 합친 값: 기준점 ±5 km ∪ 받은 지형으로 자른 선
* (선마다 `__bundle` 묶음 번호 · `__line` 묶음 안 차례) + 가닥 · 길이 m · 원본 선 수 · 좌표계 ·
* 잘린 상자([서, 남, 동, 북] 경위도) · 기준점 ~ 가장 가까운 임도 m · 마지막 올린 파일 · 묶음 줄. */
export interface PlanForestRoads {
bundles: PlanRoadBundle[];
count: number;
length_m: number;
total: number;
crs: string[];
bounds: [number, number, number, number] | null;
distance_m: number | null;
source: { files: string[]; bytes: number; uploaded_at: string } | null;
collection: GeoJsonCollection;
}
/** 올린 적 없으면 오류(404 글 그대로). */
export const fetchPlanForestRoads = (projectId: string): Promise<PlanForestRoads> =>
request(`${base(projectId)}/layers/forest-roads`);
/** 묶음 하나 지움(605-1) — 남은 묶음 합친 값 · 하나도 안 남으면 null. */
export async function deletePlanForestRoad(
projectId: string,
bundle: string,
): Promise<PlanForestRoads | null> {
const response = await fetch(
`${API_BASE_URL}${base(projectId)}/layers/forest-roads/${encodeURIComponent(bundle)}`,
{ method: "DELETE", credentials: "include" },
);
const body = (await response.json().catch(() => ({}))) as PlanForestRoads & {
detail?: unknown;
};
if (!response.ok)
throw new Error(typeof body.detail === "string" ? body.detail : `HTTP ${response.status}`);
return body.bundles.length ? body : null;
}
/** 임도망도 SHP 올리기(zip 하나 · shp · shx · dbf · prj 묶음) — 못 읽으면 서버 글 그대로 오류. */
export async function uploadPlanForestRoads(
projectId: string,
files: File[],
): Promise<PlanForestRoads> {
const form = new FormData();
for (const file of files) form.append("files", file);
const response = await fetch(`${API_BASE_URL}${base(projectId)}/layers/forest-roads`, {
method: "POST",
credentials: "include",
body: form,
});
const body = (await response.json().catch(() => ({}))) as PlanForestRoads & {
detail?: unknown;
};
if (!response.ok)
throw new Error(typeof body.detail === "string" ? body.detail : `HTTP ${response.status}`);
return body;
}
/** 산사태취약지역(584-3) — 받은 범위 안 점 · 지번 맞는 필지 테두리 · 시도별 표 · 표 없는 시도. */
export interface PlanLandslideZones {
points: Array<{
lon: number;
lat: number;
kind: string | null;
area_m2: number | null;
place: string;
}>;
parcels: GeoJsonCollection;
sidos: Array<{ code: string; name: string; rows: number; located: number; basis?: string }>;
missing: string[];
/** 지적도를 받았나(못 받으면 점만). */
cadastral: boolean;
}
export const fetchPlanLandslideZones = (projectId: string): Promise<PlanLandslideZones> =>
request(`${base(projectId)}/layers/landslide-zones`);
+133
View File
@@ -0,0 +1,133 @@
/* =============================================================================
* map_layer_bar.ts
* 공용 지도 겹 줄(PLAN 569-2 · 586-2 · 624-2 · 634-2 · 요구 530 · 546) — 겹 단추 한 줄(공용 지도 도구줄 `createMapTools`):
* 단독 단추(표 묶음 없음) | 묶음 단추(여럿 고르기 메뉴 · 하나라도 켜면 눌림 · 골라도 안 닫힘) | 그래픽 카드 단추(GL ↔ 옛 캔버스).
* 보일 겹 · 묶음 · 호버제목 · 처음 켬 · 취향 열쇠 = 표(`map_layer_list`) · 누르면 켬끔 뒤집고 취향에 적고 `onToggle`.
* 막힘 = 지형을 아직 안 받음(`ready: false`) · 조각만 쓰는 겹은 옛 캔버스에서 · 페이지 까닭(`why`) — 「제목 — 까닭」.
* 받기 · 그리기 · 범례는 페이지 몫 — 켬끔 값(`on`)은 같은 객체를 같이 씀.
* 쓰는 법 = `createMapLayerBar({ page: "d03", glOn, onToggle, onGl })` → `{ root, bar, on, toggle, save, sync }`.
* ========================================================================== */
import { createIconButton } from "@ui/ui_template_button";
import { createPickMenu, type PickMenu } from "@ui/ui_template_context_menu";
import { createMapTools, mapToolTip } from "@ui/ui_template_map_tools";
import { writeByKey } from "../b_page_state";
import { tileOnly } from "./map_forest_layers";
import {
LAYER_GROUPS,
MAP_LAYER_PAGES,
pageLayers,
readLayerPref,
type LayerGroup,
type MapLayerKey,
type MapPage,
} from "./map_layer_list";
/** 그래픽 카드 단추 호버(표 밖 · 그룹1 끝 단독). */
const GL_TITLE = "그래픽 카드";
export interface MapLayerBar {
/** 도구줄 감싼 것(자리는 페이지 CSS · 한 행 = `createMapToolRow`). */
root: HTMLElement;
/** 도구줄 자체(폭 재기). */
bar: HTMLElement;
/** 켬끔 — 페이지가 바꾸면 `save` · 받기 실패로 끄는 것은 안 적음. */
on: Record<MapLayerKey, boolean>;
/** 뒤집고 적고 `onToggle`. */
toggle: (key: MapLayerKey) => void;
save: () => void;
/** 단추 · 메뉴 줄의 눌림 · 막힘 · 글을 켬끔 값에 맞춤 · `why` = 페이지 막힘 까닭(겹마다). */
sync: (state: {
ready: boolean;
glOn: boolean;
why?: Partial<Record<MapLayerKey, string>>;
}) => void;
}
export function createMapLayerBar(opts: {
page: MapPage;
glOn: boolean;
onToggle: (key: MapLayerKey) => void;
/** 그래픽 카드 단추 누름. */
onGl: () => void;
}): MapLayerBar {
const { page } = opts;
const on = readLayerPref(page);
const save = (): void => writeByKey(MAP_LAYER_PAGES[page].pref, JSON.stringify(on));
const toggle = (key: MapLayerKey): void => {
on[key] = !on[key];
save();
opts.onToggle(key);
};
const defs = pageLayers(page);
const buttons = new Map<MapLayerKey, HTMLButtonElement>();
const menus = new Map<LayerGroup, PickMenu>();
const singles: HTMLElement[] = [];
for (const def of defs) {
if (!def.group) {
const button = createIconButton({
icon: def.icon,
title: def.title,
size: 32,
pressed: on[def.key],
onClick: () => toggle(def.key),
});
button.dataset.layer = def.key;
button.dataset.tip = def.title;
buttons.set(def.key, button);
singles.push(button);
continue;
}
if (menus.has(def.group)) continue;
const group = def.group;
const menu = createPickMenu({
multi: true,
icon: LAYER_GROUPS[group].icon,
title: LAYER_GROUPS[group].name,
items: defs
.filter((one) => one.group === group)
.map((one) => ({ value: one.key, label: one.title, icon: one.icon })),
onPick: (key) => key && toggle(key as MapLayerKey),
});
menu.root.dataset.layerGroup = group;
menus.set(group, menu);
}
for (const def of defs)
if (def.group) menus.get(def.group)!.setItem(def.key, { checked: on[def.key] });
const glButton = createIconButton({
icon: "gauge",
title: GL_TITLE,
size: 32,
pressed: opts.glOn,
onClick: opts.onGl,
});
glButton.dataset.layer = "gl";
const tools = createMapTools({
label: "지도 겹",
groups: [singles, [...menus.values()].map((menu) => menu.root), [glButton]],
});
function sync({ ready, glOn, why = {} }: Parameters<MapLayerBar["sync"]>[0]): void {
for (const def of defs) {
const reason = !ready
? "지형 받기 뒤 보임"
: (why[def.key] ??
(tileOnly(def.key) && !glOn ? "옛 그림(그래픽 카드 끔)엔 안 보임" : null));
const button = buttons.get(def.key);
if (button) {
button.setAttribute("aria-pressed", String(on[def.key]));
button.disabled = !!reason;
mapToolTip(button, reason);
continue;
}
menus.get(def.group!)!.setItem(def.key, {
checked: on[def.key],
disabled: !!reason,
title: reason ? `${def.title} — ${reason}` : def.title,
});
}
glButton.setAttribute("aria-pressed", String(glOn));
}
return { root: tools.root, bar: tools.bar, on, toggle, save, sync };
}
+76
View File
@@ -0,0 +1,76 @@
/* =============================================================================
* map_layer_data.ts
* 공용 지도 겹 자료 도움(PLAN 569-2 · 581 · 586-2 · 624-3) — D03 예정노선 · B05 평면이 같이 씀(D03 `UI_LayerDefs` 에서 옮김).
* 도로 소로 · 미분류 구분 · 물 면 도엽 이름 · 넓이 글 · 그림 읽기 · GL 자료(지물 색 붙이기 · 그림 귀퉁이 · 음영 그늘 그림).
* ========================================================================== */
import type { GlImage } from "@ui/ui_template_gl_map";
import { themeColor } from "@ui/ui_template_palette";
import type { GeoJsonCollection, PlanBand, PlanRaster } from "./map_layer_api";
/** 도로 중 소로 · 미분류 구분(옛 임도 짐작 · 겹 걷음 · 페이지 자료 셈만 씀). */
export const FOREST_ROAD_KINDS = new Set(["소로", "미분류"]);
/** 물 면 = 도엽 수계(E) 면 셋. */
export const WATER_KEYS = ["실폭하천", "수계면E003", "수계면E005"];
export const hectare = (m2: number): string => `${(m2 / 10000).toFixed(1)} ha`;
export const loadImage = (src: string): Promise<HTMLImageElement> =>
new Promise((done, fail) => {
const image = new Image();
image.onload = () => done(image);
image.onerror = () => fail(new Error("그림을 읽지 못했습니다"));
image.src = src;
});
/** GL 자료 — 지물마다 그릴 색(`_color`) · 덧 속성을 붙인 사본. */
export function glTagged(
collection: GeoJsonCollection,
color: (props: Record<string, unknown>) => string,
extra: Record<string, unknown> = {},
): GeoJsonCollection {
return {
...collection,
features: (collection.features ?? []).map((feature) => ({
...feature,
properties: { ...feature.properties, ...extra, _color: color(feature.properties ?? {}) },
})),
};
}
/** 띠 GL 자료(581-1 · 634-2) — 도로 300 m 먼저 · 계류 100 m 위 · `_band` = 겹 가름(계류 = `bands` · 도로 = `roadBands`). */
export function bandCollection(info: { stream: PlanBand; road: PlanBand }): GeoJsonCollection {
const band = (name: "stream" | "road", fallback: string): GeoJsonCollection =>
glTagged(info[name].collection, () => themeColor(`--draw-map-band-${name}`, fallback), {
_band: name,
});
return {
features: [
...(band("road", "#facc15").features ?? []),
...(band("stream", "#22d3ee").features ?? []),
],
};
}
/** GL 그림 자리 — 서버 경위도 귀퉁이(왼쪽 아래부터 돎) → 왼쪽 위부터 · 없으면 null. */
export function glImage(raster: PlanRaster, url = raster.image): GlImage | null {
const box = raster.box;
return box?.length === 4 ? { url, corners: [box[3], box[2], box[1], box[0]] } : null;
}
/** 음영 그늘 그림 — 위성 위 「곱하기 0.75」와 같은 검은 반투명(밝기 g → 투명도 0.75 · (1 − g)). */
export function shadeImage(image: HTMLImageElement): string {
const canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const context = canvas.getContext("2d")!;
context.drawImage(image, 0, 0);
const pixels = context.getImageData(0, 0, canvas.width, canvas.height);
const px = pixels.data;
for (let i = 0; i < px.length; i += 4) {
px[i + 3] = Math.round(0.75 * (255 - px[i]) * (px[i + 3] / 255));
px[i] = px[i + 1] = px[i + 2] = 0;
}
context.putImageData(pixels, 0, 0);
return canvas.toDataURL("image/png");
}
+169
View File
@@ -0,0 +1,169 @@
/* =============================================================================
* map_layer_list.ts
* 공용 지도 겹 목록(PLAN 624-2 · 요구 530 · 결정 A② · A⑧ · A⑨) — 예정노선(D03) · B05 평면이 같이 씀.
* 겹 = 요구 546 그룹1 표 한 줄 — 열쇠 · 아이콘 · 호버제목 · 아이콘 묶음(`LAYER_GROUPS` · 없음 = 단독) · 보일 페이지 · 처음 값(`on` = 예정노선).
* 페이지 표(`MAP_LAYER_PAGES`) = 처음 켬(없으면 겹의 `on`) · 켬끔 취향 열쇠.
* 선 겹 꼴 · 보호구역 색 · 산사태위험 범례 = GL 꼴(`map_layer_specs`) · 범례(`map_legend`) · 옛 캔버스가 같이 읽음.
* 쓰는 법 = 겹 줄 `createMapLayerBar({ page })`(`map_layer_bar`) · 새 겹 = `LAYERS` 한 줄 + 꼴 한 벌.
* ========================================================================== */
import type { IconName } from "@ui/ui_template_icons";
import type { LegendItem } from "@ui/ui_template_flow_legend";
import { readByKey } from "../b_page_state";
export type MapLayerKey =
| "satellite"
| "hillshade"
| "contour"
| "slope"
| "stream"
| "water"
| "road"
| "forestRoad" // 걷은 겹(임도 짐작 · 요구 532) — 목록 · 꼴 없음 · 페이지 자료 셈 이음만 남음
| "forestRoadFile"
| "nationalRoad"
| "ecoForest"
| "ecology"
| "cadastral"
| "zones"
| "soils"
| "landslide"
| "landslideZones"
| "geology" // 암상 지질도(760-1 · KIGAM 5만 · 그림 조각 + 도형)
| "faults"
| "lineaments"
| "zeroOrder"
| "bands" // 계류 100 m 띠(자료 = 띠 둘 한 벌 · `_band` 로 가름)
| "roadBands" // 도로 300 m 띠(같은 자료 · 「도로」 묶음)
| "building"
| "place";
/** 임도(업로드) 고름(605-2) — 묶음 하나 또는 그 안의 선 하나(지도 강조). */
export interface RoadPick {
bundle: string;
line?: number;
}
/** 지도가 있는 페이지. */
export type MapPage = "d03" | "b05";
/** 아이콘 묶음(요구 546 표 「아이콘 그룹명」) — 묶음 단추 = 아이콘 + ▾ + 켠 수 · 여럿 고르기 메뉴. */
export type LayerGroup = "water" | "road" | "land" | "limit";
export const LAYER_GROUPS: Record<LayerGroup, { name: string; icon: IconName }> = {
water: { name: "수계망도", icon: "streams" },
road: { name: "도로", icon: "signpost" },
land: { name: "토지·건물", icon: "parcelHouse" },
limit: { name: "제한·재해", icon: "ban" },
};
export interface LayerDef {
key: MapLayerKey;
icon: IconName;
/** 호버제목(요구 546 표) — 마우스 글 · 메뉴 줄 글. */
title: string;
/** 아이콘 묶음 — null = 단독 단추. */
group: LayerGroup | null;
/** 보일 페이지(표 「사용」). */
pages: readonly MapPage[];
/** 예정노선 처음 값 — 다른 페이지는 `MAP_LAYER_PAGES[page].on`. */
on: boolean;
}
const BOTH: readonly MapPage[] = ["d03", "b05"];
const row = (
key: MapLayerKey,
icon: IconName,
title: string,
group: LayerGroup | null,
on = false,
): LayerDef => ({ key, icon, title, group, pages: BOTH, on });
/** 그룹1 표(요구 546 · 차례 = 단추 차례) — 도로 300 m 띠는 표 밖 한 줄(「도로」 묶음 · 기능 남김). */
export const LAYERS: readonly LayerDef[] = [
row("satellite", "satellite", "위성사진", null, true),
row("hillshade", "sun", "음영기복도", null),
row("contour", "mountain", "등고선", null, true),
row("slope", "angle", "급경사", null),
row("stream", "droplet", "세류", "water", true),
row("water", "ripple", "하천", "water"),
row("zeroOrder", "arrowsJoin", "0차곡", "water"),
row("bands", "borderOuter", "계류100m", "water"),
row("road", "road", "도로", "road"),
row("nationalRoad", "route", "임도망도", "road"),
row("forestRoadFile", "map", "임도(업로드)", "road"),
row("roadBands", "roadBand", "도로300m", "road"),
row("cadastral", "borderAll", "지적도", "land"),
row("building", "building", "건물", "land"),
row("place", "mapPin", "지명", "land"),
row("zones", "shield", "보호규제구역", "limit"),
row("soils", "leaf", "산림입지도", "limit"),
row("ecoForest", "trees", "국유림·사유림", "limit"),
row("ecology", "plant", "생태자연도", "limit"),
row("landslide", "alert", "산사태위험지도", "limit"),
row("landslideZones", "alertOctagon", "산사태취약지역", "limit"),
row("geology", "strata", "암상 지질도", "limit"),
row("faults", "bolt", "단층", "limit"),
row("lineaments", "lineament", "지질구조선", "limit"),
];
/** 페이지 표 — 처음 켬(없음 = 겹의 `on`) · 켬끔 취향 열쇠. */
export const MAP_LAYER_PAGES: Record<MapPage, { on?: readonly MapLayerKey[]; pref: string }> = {
d03: { pref: "aislo:pref:d03.layers" },
// A② 처음 = 지금 그대로(등고선 · 하천)
b05: { on: ["contour", "stream"], pref: "aislo:pref:b05.layers" },
};
/** 그 페이지에 보일 겹(표 차례). */
export const pageLayers = (page: MapPage): LayerDef[] =>
LAYERS.filter((def) => def.pages.includes(page));
/** 켬끔 취향 — 없거나 깨지면 페이지 처음 값 · 안 보일 겹 = 끔. */
export function readLayerPref(page: MapPage): Record<MapLayerKey, boolean> {
const { on: first, pref } = MAP_LAYER_PAGES[page];
const on = Object.fromEntries(
LAYERS.map((def) => [def.key, first ? first.includes(def.key) : def.on]),
) as Record<MapLayerKey, boolean>;
try {
const saved = JSON.parse(readByKey(pref) ?? "{}") as Partial<Record<MapLayerKey, unknown>>;
for (const def of LAYERS)
if (typeof saved[def.key] === "boolean") on[def.key] = saved[def.key] as boolean;
} catch {
// 깨진 값 = 처음 값
}
for (const def of LAYERS) if (!def.pages.includes(page)) on[def.key] = false;
return on;
}
/** 보호구역 종류 → 그림 색 값(서버 `ZONE_KINDS` 열쇠). */
export const ZONE_COLORS: Record<string, string> = {
forest: "--draw-map-zone-forest",
baekdu: "--draw-map-zone-baekdu",
water: "--draw-map-zone-water",
park: "--draw-map-zone-park",
heritage: "--draw-map-zone-heritage",
wetland: "--draw-map-zone-wetland",
wildlife: "--draw-map-zone-wildlife",
greenbelt: "--draw-map-zone-greenbelt",
};
/** 산사태위험지도 범례(1 등급 = 가장 위험 · 색 = WMS 그림 색). */
export const LANDSLIDE_LEGEND: LegendItem[] = [1, 2, 3, 4, 5].map((grade) => ({
label: `산사태 ${grade}등급`,
color: `var(--draw-map-landslide-grade-${grade})`,
}));
/** 선 겹 — 이름 · 그림 꼴(서버 `LINE_KINDS` 열쇠 = `kind` · 없으면 0차곡). 새 선 겹 = 여기 한 줄. */
export type LineLayerKey = "faults" | "lineaments" | "zeroOrder";
export const LINE_LAYERS: Record<
LineLayerKey,
{ label: string; kind?: string; color: string; width: number; dash: number[] }
> = {
faults: { label: "단층", kind: "faults", color: "--draw-map-fault", width: 2.4, dash: [] },
lineaments: {
label: "지질구조선",
kind: "lineaments",
color: "--draw-map-lineament",
width: 1.8,
dash: [8, 4],
},
zeroOrder: { label: "0차곡", color: "--draw-map-zero-order", width: 1.8, dash: [4, 3] },
};
export const LINE_KEYS = Object.keys(LINE_LAYERS) as LineLayerKey[];
+31
View File
@@ -0,0 +1,31 @@
/* =============================================================================
* map_layer_source.ts
* 공용 지도 밖 겹 출처(PLAN 760-1 · 요구 598-4 「받아온 곳의 정보를 표기」) — 켠 겹의 받아온 곳을 범례 맨 아래 한 줄씩.
* 받아온 곳 = 서버가 부르는 곳 그대로(브이월드 WFS · 위성 · 산림청 원자료 · 생활안전지도 WMS · 지오빅데이터 …) ·
* 자체 셈 겹(음영 · 급경사 · 0차곡 · 띠) · 사용자 올림(임도(업로드)) · 자기 지형(등고선 · 세류)은 안 적음.
* ========================================================================== */
import type { LegendItem } from "@ui/ui_template_flow_legend";
import { LAYERS, type MapLayerKey } from "./map_layer_list";
/** 받아온 곳 → 겹(차례 = 범례 차례). */
export const LAYER_SOURCES: ReadonlyArray<[string, readonly MapLayerKey[]]> = [
["브이월드", ["satellite", "cadastral", "zones", "faults", "lineaments"]],
["국토지리정보원 수치지형도", ["water", "road", "building", "place"]],
["산림청", ["nationalRoad", "ecoForest", "soils"]],
["시도 산사태취약지역 현황(공공데이터포털)", ["landslideZones"]],
["환경부 생태자연도(공공데이터포털)", ["ecology"]],
["행정안전부 생활안전지도(공공누리 제4유형)", ["landslide"]],
["한국지질자원연구원 수치지질도 1:50,000(CC BY-NC)", ["geology"]],
];
/** 켠 밖 겹의 출처 줄 — 「출처: 곳 — 겹 · 겹」 · 없으면 빈 것. */
export function sourceRow(on: Record<MapLayerKey, boolean>): LegendItem[] {
const title = (key: MapLayerKey): string => LAYERS.find((def) => def.key === key)?.title ?? key;
return LAYER_SOURCES.flatMap(([where, keys]) => {
const shown = keys.filter((key) => on[key]);
return shown.length
? [{ label: `출처: ${where} — ${shown.map(title).join(" · ")}`, color: "transparent" }]
: [];
});
}
+317
View File
@@ -0,0 +1,317 @@
/* =============================================================================
* map_layer_specs.ts
* 공용 지도 GL 겹 꼴(PLAN 586-2 · 624-2 · 요구 530) — 바탕 겹 스물의 MapLibre 꼴 · 차례(아래 → 위):
* 위성 · 그림 겹(음영 · 급경사 · 산사태위험 · 생태자연도) · 면 겹(산림입지 · 경제림 · 보호구역 · 띠 · 물 면 · 건물) ·
* 선 겹(지적 · 등고선 · 도로 · 임도망도 · 임도(업로드) · 세류 · 단층 · 지질구조선 · 0차곡 · 산사태취약 필지) ·
* 점 · 글(산사태취약 · 임도망도 연도 · 종류 · 지명 · 등고 수치).
* 자료 = 받는 쪽이 지물에 붙인 `_color` · 고정 소스(위성 · 등고 · 산림 조각) 이름 = `map_forest_layers` · `ui_template_gl_map`.
* 카메라 · 지도 세우기는 페이지 몫(D03 `UI_MapGl.createGlBase` · B05 `createTmGl`) — 여기는 꼴만.
* ========================================================================== */
import type { ExpressionSpecification, FilterSpecification } from "maplibre-gl";
import { GL_FONT, type GlSpec as GlMapSpec } from "@ui/ui_template_gl_map";
import { themeColor } from "@ui/ui_template_palette";
import {
forestAreaSpecs,
forestRasterSpecs,
forestRoadLabelSpec,
forestRoadSpecs,
} from "./map_forest_layers";
import { geologySpecs } from "./map_geology";
import {
LINE_KEYS,
LINE_LAYERS,
type LineLayerKey,
type MapLayerKey,
type RoadPick,
} from "./map_layer_list";
/** 자료 소스(위성 = 조각 · 걷은 임도 짐작 = 소스 없음). */
export type GlSource = Exclude<MapLayerKey, "satellite" | "forestRoad">;
const GL_FONT_BOLD = [`${GL_FONT} SemiBold`, GL_FONT];
type GlSpec = GlMapSpec<MapLayerKey>;
const HALO = "rgba(0, 0, 0, 0.55)";
const ROUND = { "line-join": "round", "line-cap": "round" } as const;
const COLOR: ExpressionSpecification = ["get", "_color"];
/** 등고 조각 굵은 선(서버가 줌마다 정함) — 가는 선은 모두라 굵은 선은 두 번 겹쳐 짙음(캔버스처럼). */
const BOLD = ["==", ["get", "k"], 1] as unknown as FilterSpecification;
/** 캔버스 점선(px) → MapLibre 점선(선 굵기 배수). */
const dash = (px: number[], width: number): number[] => px.map((v) => v / width);
/** 선 겹 — 검은 테두리 + 선(점선이면 둘 다 같은 px 무늬). */
function haloLine(
key: MapLayerKey,
source: GlSource,
color: string,
width: number,
pattern: number[] = [],
halo = { color: HALO, width: width + 1.6 },
filter?: FilterSpecification,
): GlSpec[] {
const line = (id: string, paint: Record<string, unknown>, w: number): GlSpec =>
({
key,
id,
type: "line",
source,
...(filter ? { filter } : {}),
layout: ROUND,
paint: {
...paint,
"line-width": w,
...(pattern.length ? { "line-dasharray": dash(pattern, w) } : {}),
},
}) as GlSpec;
return [
line(`${key}-halo`, { "line-color": halo.color }, halo.width),
line(key, { "line-color": color }, width),
];
}
/** 면 겹 — 채움 + 테두리(캔버스 `fillLayer` 처럼 테두리 투명도 0.9). */
/** 띠 테두리 — 계류 · 도로 같은 꼴. */
const BAND_EDGE = { color: COLOR, width: 1.2, dash: [6, 4] };
function fillEdge(
key: MapLayerKey,
source: GlSource,
fill: string | ExpressionSpecification,
opacity: number | ExpressionSpecification,
edge: { color: string | ExpressionSpecification; width: number; dash?: number[] },
filter?: ExpressionSpecification,
): GlSpec[] {
const only = filter ? { filter } : {};
return [
{
key,
id: key,
type: "fill",
source,
...only,
paint: { "fill-color": fill, "fill-opacity": opacity },
} as GlSpec,
{
key,
id: `${key}-edge`,
type: "line",
source,
...only,
layout: ROUND,
paint: {
"line-color": edge.color,
"line-width": edge.width,
"line-opacity": 0.9,
...(edge.dash ? { "line-dasharray": dash(edge.dash, edge.width) } : {}),
},
} as GlSpec,
];
}
/** 겹 차례(아래 → 위)와 꼴 — 옛 캔버스(D03 `UI_LayerPaint`)의 색 · 굵기 · 투명도 · 차례 그대로 · `light` = 위성 밝기 상한. */
export function mapLayerSpecs(light: number, pick: RoadPick | null): GlSpec[] {
const contour = themeColor("--draw-map-contour", "#ffd68c");
const place = themeColor("--draw-map-place", "#f8fafc");
const landslideZone = themeColor("--draw-map-landslide-zone", "#e11d48");
const raster = (
key: "satellite" | "hillshade" | "slope",
opacity: number,
extra: Record<string, number> = {},
): GlSpec =>
({
key,
id: key,
type: "raster",
source: key,
paint: { "raster-opacity": opacity, "raster-fade-duration": 0, ...extra },
}) as GlSpec;
const lineKind = (key: LineLayerKey): GlSpec[] => {
const { color, width, dash } = LINE_LAYERS[key];
return haloLine(key, key, themeColor(color, "#f43f5e"), width, dash);
};
return [
// 위성 = 조금 어둡고 연하게(요구 475 · 노선 선 대비) — 밝기 상한은 사용자 조절
raster("satellite", 1, { "raster-brightness-max": light, "raster-saturation": SAT_SATURATION }),
raster("hillshade", 1), // 위성 위 = 「곱하기」와 같은 검은 그늘 그림(받는 쪽이 바꿈)
raster("slope", 0.55),
...forestRasterSpecs(), // 산사태위험 · 생태자연도 = 조각 그림(622-2)
...forestAreaSpecs(), // 산림입지도 · 경제림 = 벡터 조각
...geologySpecs(), // 암상 지질도 면 · 글(760-1)
...fillEdge("zones", "zones", COLOR, 0.22, { color: COLOR, width: 1.6 }),
// 띠 = 한 자료(`_band`) · 겹 둘(634-2 · 도로 300 m 아래 · 계류 100 m 위 — 옛 한 겹 차례 그대로)
...fillEdge("roadBands", "bands", COLOR, 0.16, BAND_EDGE, ["==", ["get", "_band"], "road"]),
...fillEdge("bands", "bands", COLOR, 0.18, BAND_EDGE, ["==", ["get", "_band"], "stream"]),
{
key: "cadastral",
id: "cadastral",
type: "line",
source: "cadastral",
layout: ROUND,
paint: {
"line-color": themeColor("--draw-map-parcel", "#f0abfc"),
"line-width": 0.8,
"line-opacity": 0.8,
},
} as GlSpec,
...((): GlSpec[] => {
const water = themeColor("--draw-map-water", "#0ea5e9");
return fillEdge("water", "water", water, 0.45, { color: water, width: 1 });
})(),
...fillEdge("building", "building", themeColor("--draw-map-building", "#fb7185"), 0.7, {
color: HALO,
width: 0.6,
}),
{
key: "contour",
id: "contour-minor",
type: "line",
source: "contour",
"source-layer": "contour",
layout: ROUND,
paint: { "line-color": contour, "line-width": 0.8, "line-opacity": 0.7 },
} as GlSpec,
{
key: "contour",
id: "contour-index",
type: "line",
source: "contour",
"source-layer": "contour",
filter: BOLD,
layout: ROUND,
paint: { "line-color": contour, "line-width": 1.6, "line-opacity": 0.7 },
} as GlSpec,
{
key: "contour",
id: "contour-label",
type: "symbol",
source: "contour",
"source-layer": "contour",
filter: BOLD,
layout: {
"symbol-placement": "line",
"symbol-spacing": 320,
"text-field": ["to-string", ["get", "h"]],
"text-font": [GL_FONT],
"text-size": 10,
"text-max-angle": 30,
"text-padding": 2,
},
paint: {
"text-color": contour,
"text-halo-color": "rgba(0, 0, 0, 0.6)",
"text-halo-width": 1.5,
},
} as GlSpec,
...haloLine("road", "road", themeColor("--draw-map-road", "#f8fafc"), 1.6, [], {
color: HALO,
width: 3.2,
}),
...forestRoadSpecs(), // 임도망도(전국 국가임도) = 벡터 조각 · 임도(업로드) 아래
...haloLine(
"forestRoadFile",
"forestRoadFile",
themeColor("--draw-map-forest-road-file", "#ff2fd0"),
2.4,
[],
{ color: HALO, width: 4 },
),
// 고른 묶음 · 선 강조(605-2) — 같은 자료에서 걸러 한 겹 더 굵게
...["-pick-halo", "-pick"].map(
(tail, i) =>
({
key: "forestRoadFile",
id: `forestRoadFile${tail}`,
type: "line",
source: "forestRoadFile",
filter: pickFilter(pick),
layout: ROUND,
paint: {
"line-color": i ? themeColor("--draw-map-sketch", "#ffe94d") : HALO,
"line-width": i ? 4.2 : 7.4,
},
}) as GlSpec,
),
{
key: "stream",
id: "stream",
type: "line",
source: "stream",
layout: ROUND,
paint: {
"line-color": themeColor("--draw-map-stream", "#38bdf8"),
"line-width": ["case", ["==", ["get", "구분"], "세류"], 1, 2.4],
},
} as GlSpec,
...LINE_KEYS.flatMap(lineKind),
// 산사태취약 — 필지 = 「이 필지 안 어딘가」 테두리만 · 점 = 지정 자리
...haloLine(
"landslideZones",
"landslideZones",
landslideZone,
1.8,
[6, 3],
{ color: "rgba(255, 255, 255, 0.7)", width: 3.2 },
["!=", ["geometry-type"], "Point"] as unknown as FilterSpecification,
),
{
key: "landslideZones",
id: "landslideZones-point",
type: "circle",
source: "landslideZones",
filter: ["==", ["geometry-type"], "Point"],
paint: {
"circle-radius": 5,
"circle-color": landslideZone,
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 1.5,
},
} as GlSpec,
forestRoadLabelSpec(), // 임도망도 글 — 지명 아래(겹치면 지명이 앞섬)
{
key: "place",
id: "place-dot",
type: "circle",
source: "place",
paint: { "circle-radius": 2.5, "circle-color": place },
} as GlSpec,
{
key: "place",
id: "place",
type: "symbol",
source: "place",
layout: {
"text-field": ["get", "name"],
"text-font": GL_FONT_BOLD,
"text-size": 11,
"text-anchor": "bottom",
"text-offset": [0, -0.36],
"text-padding": 2,
},
paint: {
"text-color": place,
"text-halo-color": "rgba(0, 0, 0, 0.7)",
"text-halo-width": 1.5,
},
} as GlSpec,
];
}
/** 고른 묶음 · 선만 거름(고름 없음 = 아무것도 안 맞는 거름). */
export function pickFilter(pick: RoadPick | null): FilterSpecification {
const all: unknown[] = ["all", ["==", ["get", "__bundle"], pick?.bundle ?? ""]];
if (pick?.line !== undefined) all.push(["==", ["get", "__line"], pick.line]);
return all as unknown as FilterSpecification;
}
/** 겹 색 지문 — 밝음 · 어두움이 바뀌면 색을 다시. */
export const MAP_THEME_NAMES = [
"--draw-map-contour",
"--draw-map-road",
"--draw-map-stream",
"--draw-map-parcel",
] as const;
/** 위성 채도(−1~1) — 조금 연하게. */
const SAT_SATURATION = -0.3;
+215
View File
@@ -0,0 +1,215 @@
/* =============================================================================
* map_legend.ts
* 공용 지도 범례(PLAN 569-2 · 622-2 · 624-2 · 요구 530) — 켠 겹 범례 칸 하나(줄마다 `createLegend`) + 페이지가 붙이는 덧 줄.
* 조각 겹 범례(산사태위험 · 임도망도 · 경제림 · 생태자연도) = 받을 자료 없음 → 켬끔만 보고 여기서 만듦(`tileLegendRows`).
* 받은 자료 범례 줄(수 · 넓이 · 급경사 범위 · 624-3 D03 에서 옮김) = `legendRows(on, data)` — 받은 자료 꼴은 `LegendData`
* (D03 옛 캔버스 자료 · B05 GL 자료 둘 다 맞음 · 받는 일은 페이지 몫).
* 암상 지질도 줄 · 노선 구간 표 = `map_geology`(760-1) · 켠 밖 겹 출처 줄 = 맨 아래(`map_layer_source` · 598-4).
* 쓰는 법 = `const legend = createMapLegend("d03-plan__legend")` → `legend.show(rows, [덧 줄])` · 줄 · 덧 없으면 숨김.
* ========================================================================== */
import { createLegend, type LegendItem } from "@ui/ui_template_flow_legend";
import { forestLegendRows } from "./map_forest_layers";
import { geologyRow, type PlanGeology } from "./map_geology";
import type {
PlanBand,
PlanForestRoads,
PlanLandslideZones,
PlanRaster,
PlanSoils,
PlanZones,
} from "./map_layer_api";
import { hectare } from "./map_layer_data";
import { sourceRow } from "./map_layer_source";
import {
LANDSLIDE_LEGEND,
LINE_KEYS,
LINE_LAYERS,
ZONE_COLORS,
type MapLayerKey,
} from "./map_layer_list";
import { SLOPE_MAX_PCT, bandRatios, pctToDeg, type SlopeBand } from "./logic/map_slope_bands";
/** 지물 수만 보는 겹(옛 캔버스 자료 · GeoJSON 둘 다). */
interface Counted {
features?: ArrayLike<unknown>;
}
/** 범례가 읽는 받은 자료 — 받은 겹만. */
export interface LegendData {
slope: { raster: PlanRaster; bands: SlopeBand[] };
landslideZones: { info: PlanLandslideZones };
zones: { summary: PlanZones["summary"] };
/** 토양형(코드 차례) 색 값 · 요약. */
soils: { kinds: Array<{ color: string }>; summary: PlanSoils["summary"] };
bands: { info: { stream: PlanBand; road: PlanBand } };
water: Counted;
building: Counted;
road: { small: Counted };
forestRoadFile: { info: PlanForestRoads };
faults: { count: number; length_m: number };
lineaments: { count: number; length_m: number };
zeroOrder: { count: number; length_m: number };
geology: PlanGeology;
}
export interface MapLegend {
root: HTMLElement;
show: (rows: LegendItem[][], tail?: HTMLElement[]) => void;
}
/** 켠 조각 겹 범례 — 산사태위험 다음 산림 셋. */
export const tileLegendRows = (on: Record<MapLayerKey, boolean>): LegendItem[][] => [
...(on.landslide ? [LANDSLIDE_LEGEND] : []),
...forestLegendRows(on),
];
/** `className` = 자리 · 꼴(페이지 CSS). */
export function createMapLegend(className: string): MapLegend {
const root = document.createElement("div");
root.className = className;
root.title = "켠 겹 범례 — 받은 지형 범위 안 수 · 넓이";
return {
root,
show(rows, tail = []) {
root.replaceChildren(...rows.map((row) => createLegend(row)), ...tail);
root.hidden = !rows.length && !tail.length;
},
};
}
const km = (m: number): string => `${(m / 1000).toFixed(2)} km`;
/** 급경사 범례 — 범위마다 「아래~위 %(°) · 비율」(위 = 끝 = 「↑」). */
function slopeRow({ raster, bands }: LegendData["slope"]): LegendItem[] {
const ratios = bandRatios(raster.counts ?? [], bands);
const deg = (pct: number): string => pctToDeg(pct).toFixed(0);
return bands.map((band, i) => {
const open = band.high >= SLOPE_MAX_PCT;
const range = open
? `${band.low} %↑(${deg(band.low)}°↑)`
: `${band.low}~${band.high} %(${deg(band.low)}~${deg(band.high)}°)`;
return { label: `${range} · ${(ratios[i] * 100).toFixed(1)} %`, color: `var(${band.color})` };
});
}
/** 산사태취약지역 범례 — 점 · 필지 · 위치 없는 시도 · 표 없는 시도 · 지적 못 받음. */
function landslideZoneRow({ info }: LegendData["landslideZones"]): LegendItem[] {
const color = "var(--draw-map-landslide-zone)";
const items: LegendItem[] = [
{
label: `산사태취약지역 점 ${info.points.length}곳`,
color,
kind: "dot",
off: !info.points.length,
},
{
label: `지번 맞는 필지 ${info.parcels.features?.length ?? 0}곳(테두리)`,
color,
kind: "dash",
off: !info.parcels.features?.length,
},
];
for (const sido of info.sidos)
if (!sido.located) items.push({ label: `${sido.name} 위치 없음 — 필지만`, color, off: true });
for (const name of info.missing)
items.push({ label: `${name} — 이 시도 자료 없음`, color: "transparent", off: true });
if (!info.cadastral)
items.push({ label: "지적도 못 받음 — 필지 테두리 없음", color: "transparent", off: true });
return items;
}
/** 켠 겹의 받은 자료 범례 줄(급경사 · 조각 겹 · 산사태취약 · 보호구역 · 토양 · 띠 · 선 겹 · 물 면 · 건물 · 임도). */
export function legendRows(
on: Record<MapLayerKey, boolean>,
data: Partial<LegendData>,
): LegendItem[][] {
const rows: LegendItem[][] = [];
if (on.slope && data.slope) rows.push(slopeRow(data.slope));
rows.push(...tileLegendRows(on)); // 조각 겹 = 받기 없음(622-2 · 공용 범례)
if (on.landslideZones && data.landslideZones) rows.push(landslideZoneRow(data.landslideZones));
if (on.zones && data.zones) {
const items = Object.entries(data.zones.summary)
.filter(([, kind]) => kind.count || kind.failed)
.map(([key, kind]) => ({
label: kind.failed
? `${kind.label} 받지 못함`
: `${kind.label} ${kind.count}곳 ${hectare(kind.area_m2)}`,
color: `var(${ZONE_COLORS[key]})`,
off: !!kind.failed,
}));
rows.push(
items.length
? items
: [{ label: "보호 · 규제 구역 없음(받은 범위 안)", color: "transparent", off: true }],
);
}
if (on.soils && data.soils) {
const { kinds, summary } = data.soils;
rows.push(
summary.length
? summary.map((soil, i) => ({
label: `${soil.name} ${hectare(soil.area_m2)}`,
color: `var(${kinds[i].color})`,
}))
: [{ label: "산림입지도 없음(받은 범위 안)", color: "transparent", off: true }],
);
}
// 계류 100 m 띠 · 도로 300 m 띠 = 겹 둘(634-2 · 자료 한 벌)
if ((on.bands || on.roadBands) && data.bands) {
const { stream, road } = data.bands.info;
rows.push([
...(on.bands
? [
{
label: `계류 ${stream.dist_m} m 띠 ${hectare(stream.area_m2)} (${(stream.ratio * 100).toFixed(1)} %)`,
color: "var(--draw-map-band-stream)",
},
]
: []),
...(on.roadBands
? [
{
label: `도로 ${road.dist_m} m 띠 ${hectare(road.area_m2)} (${(road.ratio * 100).toFixed(1)} %)`,
color: "var(--draw-map-band-road)",
},
]
: []),
]);
}
const lines = LINE_KEYS.flatMap((key) => {
const got = on[key] && data[key];
if (!got) return [];
const { label, color, dash } = LINE_LAYERS[key];
return [
{
label: `${label} ${got.count}가닥 ${km(got.length_m)}`,
color: `var(${color})`,
kind: dash.length ? ("dash" as const) : ("line" as const),
off: !got.count,
},
];
});
if (lines.length) rows.push(lines);
if (on.geology && data.geology) rows.push(geologyRow(data.geology));
const tail: LegendItem[] = [];
if (on.water && data.water)
tail.push({
label: `물 면 ${data.water.features?.length ?? 0}곳`,
color: "var(--draw-map-water)",
});
if (on.building && data.building)
tail.push({
label: `건물 ${(data.building.features?.length ?? 0).toLocaleString()}동`,
color: "var(--draw-map-building)",
});
if (on.forestRoadFile && data.forestRoadFile)
tail.push({
label: `임도(업로드) ${data.forestRoadFile.info.count}가닥 ${km(data.forestRoadFile.info.length_m)}`,
color: "var(--draw-map-forest-road-file)",
kind: "line",
});
if (tail.length) rows.push(tail);
rows.push(...sourceRow(on).map((item) => [item]));
return rows;
}
+117
View File
@@ -0,0 +1,117 @@
/* =============================================================================
* map_lidar_upload.ts
* 공용 [LAS 올리기](PLAN 637-3 · 요구 546) — 예정노선(D03) · B05 지형 칩 옆 같은 단추 · 같은 흐름.
* 단추 → 파일 고름(LAS · LAZ + .prj) → `POST <path>`(multipart `files` · 못 받으면 서버 글 그대로 알림)
* → 1 초마다 `poll` 로 진행(`show` = 페이지 진행 서클) → 끝(오류면 알림) → 서클 · 단추 풀고 `onDone(마지막 진행)`.
* 주소 · 진행 묻기 · 서클 · 끝난 뒤 일은 페이지 인자(D = `/plan/lidar` · B = `/route/lidar`).
* 지켜보는 동안 단추 막힘 · 페이지를 떠나면(단추가 문서 밖) 그침.
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
import { createButton } from "@ui/ui_template_button";
import { pickFiles } from "@ui/ui_template_drop";
import { showToast } from "@ui/ui_template_elements";
const POLL_MS = 1000;
/** 서버 뒤 일 진행(`common_util_lidar_upload.start_job` 꼴). */
export interface LidarJob {
running: boolean;
percent: number;
message?: string | null;
error?: string | null;
interrupted?: boolean;
}
export interface LidarUpload {
/** [LAS 올리기] 단추(보임은 페이지 몫). */
button: HTMLButtonElement;
/** 도는 일을 끝날 때까지 지켜봄(들어올 때 이어 보기). */
follow: () => Promise<void>;
/** 지켜보는 중. */
busy: () => boolean;
}
const errorText = (error: unknown): string =>
error instanceof Error ? error.message : String(error);
/** LAS 묶음 올리기 — 못 받으면 서버 `detail` 글 그대로 오류. */
async function postFiles(path: string, files: File[]): Promise<void> {
const form = new FormData();
for (const file of files) form.append("files", file);
const response = await fetch(`${API_BASE_URL}${path}`, {
method: "POST",
credentials: "include",
body: form,
});
if (response.ok) return;
const body = (await response.json().catch(() => ({}))) as { detail?: unknown };
throw new Error(typeof body.detail === "string" ? body.detail : `HTTP ${response.status}`);
}
export function createLidarUpload<J extends LidarJob>(opts: {
/** 올리기 주소(`API_BASE_URL` 뒤). */
path: string;
/** 진행 한 번 묻기. */
poll: () => Promise<J>;
/** 진행 보임(0~1 · 글) · null = 끝. */
show: (state: { ratio: number; text: string } | null) => void;
/** 지켜보기를 감쌈(예: 서클 일 셈). */
track?: (job: Promise<void>) => Promise<void>;
/** 지켜보기 시작 · 끝. */
onBusy?: (busy: boolean) => void;
/** 짓기가 끝난 뒤(오류 알림 뒤 · 단추 풀린 뒤 · 마지막 진행). */
onDone: (job: J) => Promise<void> | void;
}): LidarUpload {
let busy = false;
const button = createButton({
label: "LAS 올리기",
variant: "ghost",
size: 32,
iconStart: "upload",
onClick: () =>
pickFiles({ accept: ".las,.laz,.prj", multiple: true }, (files) => void send(files)),
});
const setBusy = (on: boolean): void => {
busy = on;
button.disabled = on;
opts.onBusy?.(on);
};
async function send(files: File[]): Promise<void> {
try {
await postFiles(opts.path, files);
} catch (error) {
return showToast(`LAS 올리지 못함 — ${errorText(error)}`, "error");
}
await follow();
}
async function watch(): Promise<void> {
let last: J | null = null;
setBusy(true);
opts.show({ ratio: 0, text: "" });
try {
while (button.isConnected) {
const job = await opts.poll();
opts.show({ ratio: job.percent / 100, text: job.message ?? "" });
if (!job.running) {
if (job.error) showToast(`라이다 지형 못 지음 — ${job.error}`, "error");
last = job;
break;
}
await new Promise((done) => setTimeout(done, POLL_MS));
}
} catch (error) {
showToast(`라이다 진행을 읽지 못했습니다 — ${errorText(error)}`, "error");
} finally {
opts.show(null);
setBusy(false);
}
if (last) await opts.onDone(last);
}
const follow = (): Promise<void> => (opts.track ? opts.track(watch()) : watch());
return { button, follow, busy: () => busy };
}
+57
View File
@@ -0,0 +1,57 @@
/* =============================================================================
* map_route_preview.ts
* 공용 「추천노선」 미리보기(PLAN 626-1 → 636-2 공용 · 요구 530 · 546) — B05 평면 · 예정노선(D03)이 같이 씀.
* 메뉴 = 도구줄 아이콘 하나(compass · 호버 「추천노선」) + 아래 판(여럿 고르기 · 줄마다 색 점 · 「끔」 줄 없음 ·
* 다 끄면 꺼짐 · 부품 `createPickMenu({ multi })` · 589-12).
* 그리기 = 지도 위 선(노랑 `--draw-map-sketch` · 검은 테 · 기본 점선) — 위성 · 어두운 바탕 모두 보임.
* 항목 · 선 셈은 페이지 몫: B05 = 노선 추천 저장본 넷(`B05_Profile_UI_RouteEdit_RecPreview`) ·
* 예정노선 = 가이드 선(`D03_PlanAnalysis_UI_Guide`).
* ========================================================================== */
import { createPickMenu, type PickMenu, type PickMenuItem } from "@ui/ui_template_context_menu";
import { themeColor } from "@ui/ui_template_palette";
type Vertex = [number, number] | [number, number, number];
/** 「추천노선」 메뉴 — 줄 누름 = `onPick(값)`(켬끔 바뀜 · 지금 켠 줄은 `menu.values()`). */
export function createRoutePreviewMenu(opts: {
items: PickMenuItem[];
onPick: (value: string) => void;
}): PickMenu {
const menu = createPickMenu({
multi: true,
icon: "compass",
title: "추천노선", // 마우스 글 = 공용 표 호버제목(634-2 · `mapToolGroups` 가 맞춤)
items: opts.items,
onPick: (value) => value && opts.onPick(value),
});
menu.root.dataset.act = "recommend";
return menu;
}
/** 미리보기 선 하나 — 검은 테(5 px) 위 색 선(3 px) · 기본 노랑 점선. */
export function paintRoutePreview(
context: CanvasRenderingContext2D,
line: readonly Vertex[],
toScreen: (vertex: [number, number]) => [number, number],
style: { dash?: number[]; color?: string } = {},
): void {
if (line.length < 2) return;
context.save();
context.setLineDash(style.dash ?? [10, 6]);
context.lineJoin = "round";
context.beginPath();
line.forEach((vertex, index) => {
const [x, y] = toScreen([vertex[0], vertex[1]]);
if (index) context.lineTo(x, y);
else context.moveTo(x, y);
});
// 지도 위 그림 색(테마 안 따름) + 검은 테 — 위성 · 어두운 바탕 모두 보임
context.strokeStyle = "rgba(0, 0, 0, 0.6)";
context.lineWidth = 5;
context.stroke();
context.strokeStyle = style.color ?? themeColor("--draw-map-sketch", "#ffe94d");
context.lineWidth = 3;
context.stroke();
context.restore();
}
+39
View File
@@ -0,0 +1,39 @@
/* =============================================================================
* map_sat_light.ts
* 공용 지도 위성 바탕 밝기(D03 · B05 · 624-3 공용으로 옮김)(요구 475) — 노선 선 대비를 위해 위성을 조금 어둡게 · 사용자가 범례에서 조절.
* 값 = 밝기 상한(0.4~1) · 계정 화면 취향으로 기억. 칠하기 = D03 `UI_MapGl` `setSatelliteLight` · B05 `UI_RouteEdit_Layers`.
* ========================================================================== */
import { createSlider } from "@ui/ui_template_input";
import { readByKey, writeByKey } from "../b_page_state";
const PREF_KEY = "aislo:pref:d03.satellite-light";
export const SAT_LIGHT_MIN = 0.4;
export const SAT_LIGHT_MAX = 1;
export const SAT_LIGHT_DEFAULT = 0.8;
/** 기억한 밝기 — 없거나 깨지면 처음 값. */
export function readSatLight(): number {
const value = Number(readByKey(PREF_KEY));
return readByKey(PREF_KEY) !== null && value >= SAT_LIGHT_MIN && value <= SAT_LIGHT_MAX
? value
: SAT_LIGHT_DEFAULT;
}
/** 범례 「위성 밝기」 칸 — 움직이면 바로 `onChange` · 기억. */
export function createSatLightRow(value: number, onChange: (value: number) => void): HTMLElement {
const { root, input } = createSlider({
label: "위성 밝기",
min: SAT_LIGHT_MIN,
max: SAT_LIGHT_MAX,
step: 0.05,
value,
format: (v) => `${Math.round(v * 100)} %`,
onInput: (v) => {
writeByKey(PREF_KEY, String(v));
onChange(v);
},
});
input.dataset.role = "satellite-light";
return root;
}
+201
View File
@@ -0,0 +1,201 @@
/* =============================================================================
* map_slope_bands.ts
* 공용 지도 급경사 범위 옵션(D03 · B05 · 624-3 공용으로 옮김)(PLAN 603 · 요구 466 · 467 · 468) — 범례 급경사 줄 ⚙ → 모달
* (631 · 요구 537 = 수평 바 `ui_template_range_bar` — 칸 사이 경계값 한 번 · 아래 ° 환산 · 칸 눌러 색 · 칠 안 함 칸 ·
* [단계 추가] = 고른 칸 반으로 · [단계 지움] = 이웃과 합침 · [기본값] · [적용] · 빈 값 · 같은 값 · 거꾸로 막음).
* 기억 = 화면 취향(`aislo:pref:d03.slope-bands` · 계정에 붙음 · 프로젝트 무관).
* 칠하기 = 서버 값 그림(회색 · 칸 = 기울기 % 내림) → 범위 색 그림(서버 왕복 없음 · CLAUDE.md 5장).
* 범위 · 비율 셈 = `logic/map_slope_bands.ts` · 부르는 곳 = D03 `UI_Layers` · B05 `UI_RouteEdit_Layers`.
* ========================================================================== */
import { createButton, createIconButton } from "@ui/ui_template_button";
import { openModal } from "@ui/ui_template_modal";
import { themeColor } from "@ui/ui_template_palette";
import { createRangeBar } from "@ui/ui_template_range_bar";
import { createText } from "@ui/ui_template_text";
import { readByKey, writeByKey } from "../b_page_state";
import { loadImage } from "./map_layer_data";
import {
DEFAULT_SLOPE_BANDS,
SLOPE_COLORS,
SLOPE_MAX_BANDS,
SLOPE_MAX_PCT,
bandOf,
bandsToBar,
barToBands,
checkBands,
checkEdges,
mergeBar,
parseBands,
pctToDeg,
splitBar,
type SlopeBand,
type SlopeBar,
} from "./logic/map_slope_bands";
const PREF_KEY = "aislo:pref:d03.slope-bands";
/** 칠한 칸 투명도(겹 투명도 0.55 는 따로). */
const ALPHA = 150;
export const readSlopeBands = (): SlopeBand[] => parseBands(readByKey(PREF_KEY));
export const writeSlopeBands = (bands: SlopeBand[]): void =>
writeByKey(PREF_KEY, JSON.stringify(bands));
/** 값 그림 → 칸 값(빨강 자리 = 회색 값) · 받은 blob 주소는 다 읽었으니 놓음(616-4). */
export function slopeValues(image: HTMLImageElement): ImageData {
const canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const context = canvas.getContext("2d", { willReadFrequently: true })!;
context.drawImage(image, 0, 0);
if (image.src.startsWith("blob:")) URL.revokeObjectURL(image.src);
return context.getImageData(0, 0, canvas.width, canvas.height);
}
/** 범위대로 칠한 그림(주소 = data URL · toBlob 의 한가 작업 기다림 최대 1 s 없앰 616-4 ·
* 부르는 쪽 `URL.revokeObjectURL` 은 그대로 둬도 됨). */
export async function paintSlope(
values: ImageData,
bands: SlopeBand[],
): Promise<{ url: string; image: HTMLImageElement }> {
const rgb = bands.map((band) => hexRgb(themeColor(band.color, "#facc15")));
const lut = new Uint32Array(256);
const view = new DataView(lut.buffer);
for (let v = 0; v < 256; v++) {
const i = bandOf(v, bands);
if (i >= 0) view.setUint32(v * 4, ((rgb[i] << 8) | ALPHA) >>> 0, false);
}
const out = new ImageData(values.width, values.height);
const src = values.data;
const dst = new Uint32Array(out.data.buffer);
for (let p = 0; p < dst.length; p++) dst[p] = lut[src[p * 4]];
const canvas = document.createElement("canvas");
canvas.width = values.width;
canvas.height = values.height;
canvas.getContext("2d")!.putImageData(out, 0, 0);
const url = canvas.toDataURL("image/png");
return { url, image: await loadImage(url) };
}
/** `#rrggbb` → 0xrrggbb(못 읽으면 노랑). */
function hexRgb(color: string): number {
const hex = /^#?([0-9a-f]{6})$/i.exec(color.trim())?.[1];
return hex ? parseInt(hex, 16) : 0xfacc15;
}
/** 범례 급경사 줄 ⚙. */
export const slopeGear = (onClick: () => void): HTMLButtonElement => {
const button = createIconButton({
icon: "settings",
title: "급경사 범위 바꾸기(기울기 %)",
size: 20,
onClick,
});
button.dataset.role = "slope-bands";
return button;
};
/** 옵션 모달 — 수평 바(631 · 요구 537) · [적용] = 막을 것 없으면 `onApply` 후 닫음. */
export function openSlopeBands(current: SlopeBand[], onApply: (bands: SlopeBand[]) => void): void {
const error = createText("", "error");
error.hidden = true;
const bar = createRangeBar({
...bandsToBar(current),
palette: SLOPE_COLORS.map((c) => ({ value: c.color, name: c.name, color: `var(${c.color})` })),
noneLabel: "칠 안 함",
unit: "%",
ends: ["0 %", `${SLOPE_MAX_PCT} % 이상`],
hint: (pct) => `≈ ${pctToDeg(pct).toFixed(1)}°`,
ariaLabel: "급경사 범위",
onChange: () => refresh(),
onSelect: () => refresh(),
});
bar.root.dataset.role = "slope-bar";
/** 고칠 거리 — 경계 먼저 · 다음 범위(색 칸 없음 · 너무 많음). */
function problem(): string | null {
const state = bar.get();
return checkEdges(state.edges) ?? checkBands(barToBands(state));
}
function refresh(): void {
const state = bar.get();
const why = problem();
error.lastChild!.textContent = why ?? "";
error.hidden = !why;
const bad = checkEdges(state.edges) ? /(\d+)번째 경계/.exec(why ?? "")?.[1] : undefined;
bar.setError(bad ? Number(bad) : undefined);
addButton.disabled = !splitBar(state, bar.selected());
removeButton.disabled = !mergeBar(state, bar.selected());
}
const step =
(make: (state: SlopeBar, i: number) => SlopeBar | null, next: (i: number) => number) => () => {
const i = bar.selected();
const got = make(bar.get(), i);
if (!got) return;
bar.set(got, next(i));
refresh();
};
const addButton = createButton({
label: "단계 추가",
variant: "ghost",
size: 28,
iconStart: "plus",
onClick: step(splitBar, (i) => i + 1),
});
addButton.dataset.role = "slope-band-add";
addButton.title = "고른 칸을 반으로 나눔";
const removeButton = createButton({
label: "단계 지움",
variant: "ghost",
size: 28,
iconStart: "trash",
onClick: step(mergeBar, (i) => Math.max(0, i - 1)),
});
removeButton.dataset.role = "slope-band-remove";
removeButton.title = "고른 칸을 이웃 칸과 합침";
openModal({
title: "급경사 범위(기울기 %)",
closeLabel: "닫기",
width: "960px",
mount: (body, close, actions) => {
const reset = createButton({
label: "기본값",
variant: "ghost",
size: 28,
onClick: () => {
bar.set(bandsToBar(DEFAULT_SLOPE_BANDS), 0);
refresh();
},
});
reset.dataset.role = "slope-band-default";
const apply = createButton({
label: "적용",
size: 28,
onClick: () => {
refresh();
if (problem()) return;
onApply(barToBands(bar.get()));
close();
},
});
apply.dataset.role = "slope-band-apply";
actions.append(apply);
const foot = document.createElement("div");
foot.className = "d03-plan__row";
foot.append(addButton, removeButton, reset);
body.append(
createText(
`칸 사이 = 경계값(아래 칸 끝 = 위 칸 처음 · 아래 포함) · 칸 눌러 고르고 색 · 단계 ${SLOPE_MAX_BANDS}개까지 · 기본 = 15 · 35 · 40°(지식DB)`,
"hint",
),
bar.root,
foot,
error,
);
refresh();
},
});
}
+65
View File
@@ -0,0 +1,65 @@
/* =============================================================================
* map_terrain_chip.ts
* 공용 지도 지형 칩(PLAN 624-2 · 요구 530 · 결정 A④ · A⑤ · D2) — 도구줄 끝 분할 고르기 하나: 수치지형도 · 라이다.
* 마우스 글 = 도구줄 표 호버제목(「수치지형도/라이다」 · 634-2) · 확인창은 B05 몫.
* 없는 원천 = 막힘 + 「제목 — 까닭」(D03 라이다 = D 프로젝트에 LAS 올린 뒤 · 624-4).
* 화면 칩만 — 바꾸는 일(서버 원천 인자 624-1 · B05 설계 지형 바꿈 626-1)은 `onChange` 로 페이지가 붙임.
* 쓰는 법 = `createTerrainChip({ page: "d03", value: "sheet", have: ["sheet"] })` → `{ root, set, setHave }` · root 를 도구줄 `terrain` 칸에.
* ========================================================================== */
import { createSegment } from "@ui/ui_template_segment";
import type { MapPage } from "./map_layer_list";
import { mapToolTitle } from "./map_toolbar";
/** 서버 겹 길 `terrain=` 값과 같음(624-1 · 빈 값 = 설계 지형). */
export type TerrainSource = "sheet" | "lidar";
export const TERRAIN_NAMES: Record<TerrainSource, string> = {
sheet: "수치지형도",
lidar: "라이다",
};
/** 막힘 까닭(마우스 글 = 「수치지형도/라이다 — 까닭」 · 요구 546). */
const MISSING: Record<TerrainSource, string> = {
sheet: "이 프로젝트 범위에 도엽 지형 없음",
lidar: "올린 LAS 없음",
};
const TITLE = mapToolTitle("terrain");
export interface TerrainChip {
root: HTMLElement;
/** 고름을 바꿈(onChange 는 안 부름). */
set: (value: TerrainSource) => void;
/** 고를 수 있는 원천 — 없는 것은 막힘. */
setHave: (have: readonly TerrainSource[]) => void;
}
export function createTerrainChip(opts: {
page: MapPage;
value: TerrainSource;
have: readonly TerrainSource[];
onChange?: (value: TerrainSource) => void;
}): TerrainChip {
const sources = Object.keys(TERRAIN_NAMES) as TerrainSource[];
const segment = createSegment({
options: sources.map((value) => ({ value, label: TERRAIN_NAMES[value] })),
value: opts.value,
size: 32,
ariaLabel: TITLE,
onChange: (value) => opts.onChange?.(value as TerrainSource),
});
const root = segment.root;
root.dataset.act = "terrain";
root.title = TITLE;
const setHave = (have: readonly TerrainSource[]): void => {
for (const value of sources) {
const item = root.querySelector<HTMLButtonElement>(`[data-value="${value}"]`)!;
item.disabled = !have.includes(value);
// 열림 = 글 없음(칩 글 「수치지형도/라이다」 그대로) · 막힘 = 「제목 — 까닭」
if (item.disabled) item.title = `${TITLE} — ${TERRAIN_NAMES[value]} ${MISSING[value]}`;
else item.removeAttribute("title");
}
};
setHave(opts.have);
return { root, set: (value) => segment.set(value), setHave };
}
+95
View File
@@ -0,0 +1,95 @@
/* =============================================================================
* map_toolbar.ts
* 공용 지도 도구줄 표(PLAN 624-2 · 634-2 · 요구 530 · 546) — 예정노선(D03) · B05 평면이 같이 씀.
* 한 줄 = 요구 546 그룹2 표 한 줄 — 열쇠(`ui_template_map_tools` 단추 이름 · 아이콘) · 호버제목(`title`) · 할 일 · 보일 페이지.
* 묶음 = 나눔선 · 차례 = 도구줄 차례 · 단추 아닌 칸(간격 고르기 · 추천 노선 보기 · 지형 칩)은 페이지가 만든 요소.
* 마우스 글 = 이 표 `title` 하나(페이지 덮어쓰기 없음) · 막힘이면 페이지가 `mapToolTip(단추, 까닭)` → 「제목 — 까닭」.
* 누름 동작은 페이지 몫 — 만든 요소를 `parts` 로 넘기면 그 자리에 · 안 넘긴 단추는 기본 단추(돌리기 둘 = 공용 돌림이 `data-act` 로 찾음).
* [?] 도움말 단추 = `createMapTools` 의 `hint`(맨 앞 · 글 = 이 표 첫 줄).
* 쓰는 법 = `createMapTools({ label, hint, groups: mapToolGroups("d03", { undo: undoButton, terrain: chip.root }) })`.
* ========================================================================== */
import { mapToolButton, mapToolTip, type MapToolAct } from "@ui/ui_template_map_tools";
import type { MapPage } from "./map_layer_list";
export type MapToolKey = MapToolAct | "hint-toggle" | "recommend" | "terrain";
export interface MapToolRow {
key: MapToolKey;
/** 호버제목(요구 546 표). */
title: string;
does: string;
pages: readonly MapPage[];
}
const BOTH: readonly MapPage[] = ["d03", "b05"];
const B05: readonly MapPage[] = ["b05"];
export const MAP_TOOLBAR: readonly (readonly MapToolRow[])[] = [
[{ key: "hint-toggle", title: "도움말", does: "조작 도움말 펼치기 · 접기(`hint`)", pages: BOTH }],
[
{ key: "undo", title: "되돌리기", does: "편집 한 걸음 되돌림(Ctrl+Z)", pages: BOTH },
{ key: "redo", title: "다시하기", does: "되돌린 걸음 다시(Ctrl+Y)", pages: BOTH },
],
[
{ key: "view3d", title: "3D", does: "3D 큰 창(동작 = 페이지 몫)", pages: BOTH },
{ key: "rotate-ccw", title: "좌 회전", does: "지도 반시계로 돌림(Ctrl+휠도)", pages: BOTH },
{ key: "rotate-cw", title: "우 회전", does: "지도 시계로 돌림", pages: BOTH },
],
[
{
key: "contour-minor",
title: "등고선 1m",
does: "라이다 1 m 등고선 켬끔(라이다일 때만 열림)",
pages: B05,
},
{ key: "stations", title: "측점", does: "측점 눈금 · 번호 켬끔", pages: BOTH },
{ key: "slopes", title: "길이·기울기", does: "구간 길이 · 기울기 글 켬끔", pages: BOTH },
{ key: "footprint", title: "절성토선", does: "도로 끝 · 비탈 끝 켬끔", pages: B05 },
{ key: "measure", title: "길이측정", does: "거리 재기(동작 = 페이지 몫 · A⑩)", pages: BOTH },
],
[
{
key: "station-snap",
title: "스냅",
does: "곡선 가운데(MC)를 간격 배수에 맞춤 · 고르기 메뉴 하나(측점 No. · 10 m · 끔 · 589-4)",
pages: B05,
},
],
[
{
key: "recommend",
title: "추천노선",
does: "B05 = 노선 추천 저장본 선 여럿 고르기(색 구분) · 예정노선 = 가이드 선(지점 잇기 · 636-2)",
pages: BOTH,
},
],
[{ key: "terrain", title: "수치지형도/라이다", does: "지형 원천 보기 · 고르기", pages: BOTH }],
];
const ROWS = MAP_TOOLBAR.flat();
/** 표 호버제목 — 페이지 칸(지형 칩 막힘 글 등)이 씀. */
export const mapToolTitle = (key: MapToolKey): string =>
ROWS.find((row) => row.key === key)?.title ?? key;
const isAct = (key: MapToolKey): key is MapToolAct =>
key !== "hint-toggle" && key !== "station-snap" && key !== "recommend" && key !== "terrain";
/** 그 페이지 도구줄 묶음 — 넘긴 요소 · 기본 단추(단추 열쇠만) · 마우스 글 = 표 · 빈 묶음 뺌. */
export function mapToolGroups(
page: MapPage,
parts: Partial<Record<MapToolKey, HTMLElement>> = {},
): HTMLElement[][] {
return MAP_TOOLBAR.map((group) =>
group
.filter((row) => row.pages.includes(page))
.flatMap((row): HTMLElement[] => {
const made = parts[row.key] ?? (isAct(row.key) ? mapToolButton(row.key) : null);
if (!made) return [];
made.dataset.tip = row.title;
mapToolTip(made);
return [made];
}),
).filter((group) => group.length);
}
+133
View File
@@ -0,0 +1,133 @@
/* =============================================================================
* master_unit_swap.ts
* SK 단위 바꿈 환산(728-1) — 짝(①): `resources/master_data/scripts/master_unit.py` `swap` · `split`.
* 거울 시험 `resources/tester/test_728_1_unit_swap.py`. 단위 표 · 별칭 · 분모 읽기는 그 모듈 그대로 옮김
* (한 벌 — 고칠 때 두 파일 함께). 화면 · fetch · 저장 없음.
* ========================================================================== */
const ALIAS: Record<string, string> = {
ea: "개",
ton: "톤",
t: "톤",
l: "ℓ",
리터: "ℓ",
set: "조",
세트: "조",
hr: "시간",
};
const UNITS: Record<string, Record<string, number>> = {
무게: { g: 1, kg: 1000, 톤: 1000000 },
길이: { mm: 1, cm: 10, m: 1000, km: 1000000 },
넓이: { cm2: 1, m2: 10000 },
부피: { cm3: 1, ℓ: 1000, m3: 1000000 },
개수: { 개: 1, 본: 1, 매: 1, 개소: 1 },
};
const SCALE: Record<string, [string, number]> = {};
for (const [kind, group] of Object.entries(UNITS)) {
for (const [u, n] of Object.entries(group)) SCALE[u] = [kind, n];
}
const WEIGHT_TABLE = "CC000337";
const PER = /원\s*\/\s*(\d[\d,]*\.?\d*|\.\d+)?\s*([^\s·(\d,.][^\s·(]*)/;
export type UnitNeed = null | "단위중량" | "사용자";
export interface UnitSwap {
가: [number, string];
나: [number, string];
성격: [string | null, string | null];
/** have 1 개가 want 몇 개 — 1 단위 당 값은 ÷배 · 수량은 ×배 · 계수 없으면 null(셈 막음) */
배: number | null;
계수: UnitNeed;
근거: string;
}
export function plainUnit(unit: unknown): string {
const s = String(unit ?? "")
.normalize("NFKC")
.toLowerCase()
.replace(/\s+/g, "")
.replace(/ℓ/g, "l");
return ALIAS[s] ?? s;
}
export function unitKind(unit: unknown): string | null {
return SCALE[plainUnit(unit)]?.[0] ?? null;
}
function sameKind(a: unknown, b: unknown): boolean {
const k = unitKind(a);
return plainUnit(a) === plainUnit(b) || (k !== null && k === unitKind(b));
}
function weighs(a: unknown, b: unknown): boolean {
const ks = new Set([unitKind(a), unitKind(b)]);
return ks.size === 2 && ks.has("무게") && ks.has("부피");
}
/** `have` 1 단위가 `want` 몇 단위 — 부피 ↔ 무게는 단위중량(㎏/㎥) 있을 때만 · 못 하면 null */
function factor(have: string, want: string, density?: number | null): number | null {
const a = plainUnit(have);
const b = plainUnit(want);
if (a === b) return 1;
if (sameKind(a, b)) return SCALE[a][1] / SCALE[b][1];
if (density && weighs(a, b)) {
const gramsPerCm3 = density / 1000;
const base = SCALE[a][1] * (unitKind(a) === "부피" ? gramsPerCm3 : 1 / gramsPerCm3);
return base / SCALE[b][1];
}
return null;
}
/** 열두 자리로 다듬음 — 파이썬 Decimal 셈과 같은 값(뜬수 끝 흔들림 없앰) */
function tidy(x: number): number {
return Number(x.toPrecision(12));
}
function num(x: number): string {
return Number.isInteger(x) ? String(x) : String(x).slice(0, 10);
}
/** 기준단위 앞 수 떼기 — 「100본」 → [100, 본] · 「㎥」 → [1, ㎥] */
export function splitUnit(unit: unknown): [number, string] {
const text = String(unit ?? "").trim();
const got = text ? PER.exec(`원/${text}`) : null;
if (!got) return [1, text];
return [got[1] ? Number(got[1].replace(/,/g, "")) : 1, got[2]];
}
/** SK 단위 `have` → `want` 환산 — 같은 성격 = 배 자동 · 부피 ↔ 무게 = 단위중량 · 그 밖 = 사용자 계수
* (have 앞 수 뗀 1 단위 = want 앞 수 뗀 몇 단위 · 0 보다 커야). */
export function swapUnit(
have: string,
want: string,
density?: number | null,
coef?: number | null,
): UnitSwap {
const [na, ua] = splitUnit(have);
const [nb, ub] = splitUnit(want);
const base = { 가: [na, ua], 나: [nb, ub], 성격: [unitKind(ua), unitKind(ub)] } as Pick<
UnitSwap,
"가" | "나" | "성격"
>;
if (!ua || !ub) return { ...base, 배: 1, 계수: null, 근거: "" };
const need: UnitNeed = sameKind(ua, ub) ? null : weighs(ua, ub) ? "단위중량" : "사용자";
const f = need === "사용자" ? (coef && coef > 0 ? coef : null) : factor(ua, ub, density);
if (f === null) {
const why =
need === "단위중량"
? `단위중량(${WEIGHT_TABLE}) 골라야 셈`
: "성격 다름 · 환산 계수 넣어야 셈";
return { ...base, 배: null, 계수: need, 근거: `${have} ↔ ${want} — ${why}` };
}
const ratio = tidy((na * f) / nb);
const how =
need === "단위중량"
? ` · 단위중량 ${density}㎏/㎥`
: need === "사용자"
? ` · 계수 1 ${ua} = ${coef} ${ub}`
: "";
let step = "";
if (ratio === 1) step = plainUnit(have) === plainUnit(want) ? "" : `${have} = ${want}`;
else
step = `${have} 당 → ${want} 당 ${ratio > 1 ? `÷${num(ratio)}` : `×${num(tidy(1 / ratio))}`}`;
return { ...base, 배: ratio, 계수: need, 근거: step + how };
}
+10 -1
View File
@@ -8,6 +8,7 @@
* ========================================================================== */
import { ROUTES, DEFAULT_ROUTE, PROTECTED_ROUTES, type RoutePath } from "@config/config_frontend";
import { linkRouter, reloadIfOtherProject } from "./b_project_switch";
/**
* 여러 주소를 맡는 페이지 — 같은 페이지 안 주소 이동은 다시 만들지 않고 `show` 만 부른다
@@ -103,6 +104,8 @@ export const setLeaveGuard = (guard: (() => Promise<boolean>) | null): void => {
leaveGuard = guard;
};
export const takeLeaveGuard = (): (() => Promise<boolean>) | null => leaveGuard;
// 공용 떠남 물음(401)이 이 자리를 씀 — 함수 선언은 끌어올려져 아래 currentRoute 도 바로 씀
linkRouter({ currentRoute, setLeaveGuard });
/** 현재 프로젝트(기억한 id)로 여는 화면 — 그 프로젝트가 서버에 없으면 안내를 띄운다. */
const PROJECT_ROUTES: readonly RoutePath[] = [
@@ -116,6 +119,10 @@ const PROJECT_ROUTES: readonly RoutePath[] = [
ROUTES.B11_LOADING,
];
/** 프로젝트 화면(401) — B 단계 · 준비 화면 · 초안 길 `project: true`. */
export const isProjectRoute = (route: string): boolean =>
PROJECT_ROUTES.includes(route as RoutePath) || Boolean(draftRoute(route)?.project);
/** 지금 떠 있는 여러 주소 페이지 · 렌더 차례(늦게 끝난 옛 렌더를 버림). */
let mounted: MountedPage | null = null;
let renderSeq = 0;
@@ -222,7 +229,9 @@ export async function renderCurrentRoute(outlet: HTMLElement): Promise<void> {
}
// 기억한 현재 프로젝트가 지워졌으면 페이지 대신 안내 — 모든 B 단계 화면이 여기 한 곳을 지난다.
if (PROJECT_ROUTES.includes(route) || draft?.project) {
if (isProjectRoute(route)) {
// 다른 프로젝트 화면이면 새로 엶 — 앞 프로젝트 모듈 메모리가 안 묻어감(401)
if (reloadIfOtherProject()) return;
const { dropGoneProject } = await import("./b_workflow_nav");
if (await dropGoneProject()) {
if (seq !== renderSeq) return;
+16 -56
View File
@@ -109,7 +109,7 @@
bottom: 40px;
z-index: 20;
padding: 6px 10px;
border-radius: 4px;
border-radius: var(--radius-buttons);
background: var(--color-primary);
color: var(--color-primary-text);
}
@@ -126,6 +126,10 @@
cursor: pointer;
}
.ss-brush:hover {
background: var(--color-hover);
}
.ss-brush.is-on {
background: color-mix(in srgb, var(--color-focus-ring) 20%, var(--color-surface-raised));
box-shadow: inset 0 0 0 2px var(--color-focus-ring);
@@ -135,19 +139,17 @@
cursor: copy;
}
.ss-find {
position: absolute;
/* 찾기 = 떠 있는 판 부품(머리 없음) · 칸 · 단추 = 부품 — 여기는 자리 · 줄 배치만(553-6) */
.ss-find.ui-float-panel {
top: 4px;
right: 16px;
z-index: 20;
}
.ss-find > .ui-float-panel__body {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-find[hidden],
@@ -166,32 +168,17 @@
.ss-find__input {
width: 180px;
padding: 2px 6px;
border: 1px solid var(--color-border);
background: var(--color-surface-raised);
color: var(--color-text-body);
font: inherit;
}
.ss-find__btn {
padding: 2px 8px;
border: 1px solid var(--color-border);
border-radius: 4px;
background: var(--color-surface);
color: var(--color-text-body);
font: inherit;
cursor: pointer;
}
.ss-find__opt {
display: flex;
align-items: center;
gap: 2px;
font-size: 12px;
font-size: var(--text-caption);
}
.ss-find__status {
font-size: 12px;
font-size: var(--text-caption);
color: var(--color-text-secondary);
}
@@ -223,19 +210,11 @@
gap: 2px;
padding: 4px;
border: 1px solid var(--color-border);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-pick > button {
padding: 2px 8px;
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-text-body);
font: inherit;
cursor: pointer;
}
/* 함수 자동 완성 · 인자 도움말 */
.ss-hint {
position: absolute;
@@ -265,22 +244,17 @@
}
.ss-hint__item.is-on {
background: color-mix(in srgb, var(--color-accent) 18%, var(--color-surface-raised));
background: var(--color-accent-soft);
}
/* 데이터 메뉴 — 패널 창 · 유효성 목록 ▼ */
.ss-dialog {
position: absolute;
/* 패널 창 = 떠 있는 판 부품 — 여기는 자리 · 크기만(553-6) */
.ss-dialog.ui-float-panel {
top: 4px;
right: 16px;
z-index: 21;
max-width: min(720px, calc(100% - 32px));
max-height: calc(100% - 8px);
overflow: auto;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-dialog[hidden],
@@ -288,20 +262,6 @@
display: none;
}
.ss-dialog__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 6px 8px;
border-bottom: 1px solid var(--color-border);
font-weight: bold;
}
.ss-dialog__body {
padding: 8px;
}
.ss-list-arrow {
position: absolute;
z-index: 8;
+10 -3
View File
@@ -72,6 +72,9 @@ export interface SpreadsheetOptions {
conditions?: ConditionOptions;
/** 그림 열쇠 → 주소(요구 347-5 · 시트 `그림`) — 없으면 이름 적힌 빈 상자 */
figureSrc?: FigureSrc;
/** 칸 칠(요구 621-5) — 시트 id · 행 · 열(0 부터) → 꼴 이름 `success|warning|info|danger|muted`(흐림) + `mine`(왼쪽 띠) ·
* 공백으로 여럿 · 칸 `data-tone` 으로 나감 · 보이는 칸만 부름 · 안 주면 화면 그대로 */
칠?: (시트: string, r: number, c: number) => string | null | undefined;
}
export interface SpreadsheetHandle {
@@ -93,6 +96,8 @@ export interface SpreadsheetHandle {
/** HTML 표를 지금 시트 고른 칸(왼위)부터 붙임(105-1 도면 → 면적) — 붙여넣기와 같은 길(병합 · 되돌리기 한 번) ·
* `check` = 덮을 자리의 값 칸 · 알약 칸 수 · 범위 → false 면 안 붙임 · 붙인 범위 A1 · 읽기만 · 못 하면 null */
pasteTable(html: string, check?: PasteCheck): Promise<string | null>;
/** 보이는 칸만 다시 그림(`칠` 결과가 바뀐 뒤) — 문서는 안 읽음 */
repaint(): void;
/** 범위 고름 · 보이게 굴림(329-2 엑셀 보기 창) — `시트` = id 나 이름(없으면 지금 시트 · 다르면 그 탭으로) ·
* 범위 여럿 = 여러 고름 · 시트가 없거나 주소가 틀리면 false(고름 그대로) */
select(범위: string | string[], 시트?: string): boolean;
@@ -138,6 +143,7 @@ export function createSpreadsheet(
root,
book,
readOnly,
fill: opts.칠,
engine: createCalcEngine(book),
history: createHistory(),
selection: selectCell(firstSheet(book), 0, 0),
@@ -225,10 +231,10 @@ export function createSpreadsheet(
}
function dispatch(command: Command): void {
if (readOnly) return;
if (readOnly && command.종류 !== "열폭") return; // 읽기 전용도 열 폭은 바꿈(보기만 · 저장 · 되돌리기 없음)
const eff = apply(command);
if (!eff) return;
ctx.history.push(command, eff.undo);
if (!readOnly) ctx.history.push(command, eff.undo);
after(command, eff);
}
@@ -252,7 +258,7 @@ export function createSpreadsheet(
else grid.render();
}
select(ctx.selection, false, true); // 명령이 바꾼 뒤 — 알약 점 등 문서 종속 부품도 다시(81-2)
opts.onChange?.(structuredClone(ctx.book));
if (!readOnly) opts.onChange?.(structuredClone(ctx.book));
}
/** 칸 명령이 건드린 칸(서식만 바뀐 칸도 다시 그리게) */
@@ -369,6 +375,7 @@ export function createSpreadsheet(
return 이름;
},
pasteTable: (html, check) => pasteTableAt(ctx, html, check),
repaint: () => grid.render(),
select(range, sheetKey) {
const target = sheetKey
? ctx.book.시트.find((s) => s.id === sheetKey || s.이름 === sheetKey)
@@ -118,10 +118,15 @@ export function dialogKeys(overlay: HTMLElement, close: () => void, run?: () =>
});
}
/** body 에 뜬 창(.ui-confirm)이 다 닫히면 초점을 격자로 — 창을 연 바로 뒤에 부름 */
/** body 에 뜬 창(.ui-confirm · 모달)이 다 닫히면 초점을 격자로 — 창을 연 바로 뒤에 부름 */
export function watchClose(back: () => void): void {
const w = new MutationObserver(() => {
if (document.querySelector("body > .ui-confirm, body > .ui-confirm__panel")) return;
if (
document.querySelector(
"body > .ui-confirm, body > .ui-confirm__panel, body > .ui-modal__backdrop",
)
)
return;
w.disconnect();
back();
});
@@ -63,7 +63,7 @@
display: flex;
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 18%);
background: var(--color-scrim);
}
.ss-steps__box {
@@ -115,18 +115,10 @@
overflow: visible;
}
.ss-errtip__btn {
/* ⚠ = 아이콘 단추 부품(선 · 18) — 여기는 자리 · 경고 색만(553-6) */
.ss-errtip__btn.ui-icon-btn {
position: absolute;
width: 18px;
height: 18px;
padding: 0;
font-size: 11px;
line-height: 16px;
cursor: pointer;
color: var(--color-warning);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
}
.ss-errtip__balloon {
@@ -183,5 +175,5 @@
}
.ss-errtip__item:hover {
background: var(--color-mist-violet);
background: var(--color-hover);
}
+33 -12
View File
@@ -13,7 +13,8 @@
* · 확대 · 축소 — 값 자체를 저장하지 않는 화면 전용 CSS 배율(C 격자 뿌리에 transform) · 계약 밖.
* ========================================================================== */
import type { Workbook } from "./spreadsheet_types";
import { colIndex, colName, parseRange } from "./spreadsheet_address";
import type { Sheet, Workbook } from "./spreadsheet_types";
import type { SpreadsheetContext } from "./spreadsheet_view_types";
export type TextMeasurer = (text: string) => number;
@@ -30,13 +31,27 @@ const defaultMeasure: TextMeasurer = (text) => {
const colFromKey = (key: string): number => {
const m = /^([A-Z]+)\d+$/.exec(key);
if (!m) return -1;
let col = 0;
for (const ch of m[1]) col = col * 26 + (ch.charCodeAt(0) - 64);
return col - 1;
return m ? colIndex(m[1]) : -1;
};
/** 그 열에 든 칸 글자(식 칸은 식 글로 어림 — 계산값은 여기서 모름) 중 가장 넓은 것 + 여백. */
/** 열마다 가장 넓은 칸 글자 폭(식 칸은 식 글로 어림 — 계산값은 여기서 모름) · 여러 열 병합 칸은 뺌(제목 줄). */
function widestByColumn(sheet: Sheet, measure: TextMeasurer): Map<number, number> {
const wide = new Set<string>();
for (const text of sheet.병합 ?? []) {
const r = parseRange(text);
if (r && r.c1 > r.c0) wide.add(`${colName(r.c0)}${r.r0 + 1}`);
}
const out = new Map<number, number>();
for (const [key, cell] of Object.entries(sheet.칸)) {
const text = cell.식 !== undefined ? cell.식 : cell.값 !== undefined ? String(cell.값) : "";
const col = colFromKey(key);
if (!text || col < 0 || wide.has(key)) continue;
out.set(col, Math.max(out.get(col) ?? 0, measure(text)));
}
return out;
}
/** 그 열에 든 칸 글자 중 가장 넓은 것 + 여백. */
export function estimateColumnWidth(
book: Workbook,
sheetId: string,
@@ -45,13 +60,19 @@ export function estimateColumnWidth(
): number {
const sheet = book.시트.find((s) => s.id === sheetId);
if (!sheet) return MIN_WIDTH;
let widest = 0;
for (const [key, cell] of Object.entries(sheet.칸)) {
if (colFromKey(key) !== col) continue;
const text = cell.식 !== undefined ? cell.식 : cell.값 !== undefined ? String(cell.값) : "";
if (text) widest = Math.max(widest, measure(text));
return Math.max(MIN_WIDTH, Math.ceil((widestByColumn(sheet, measure).get(col) ?? 0) + PADDING));
}
/** 폭 정보 없는 열(엑셀 폭을 못 읽은 파일)에 글 길이로 처음 폭을 채움 — 최소 · 최대 안(읽기 전용 보기). */
export function fillMissingWidths(book: Workbook, min = 6, max = 60): void {
for (const sheet of book.시트) {
for (const [col, widest] of widestByColumn(sheet, defaultMeasure)) {
const letter = colName(col);
if (sheet.열?.[letter]?.폭 !== undefined) continue;
const 폭 = Math.min(max, Math.max(min, Math.ceil(widest + PADDING)));
sheet.열 = { ...sheet.열, [letter]: { ...sheet.열?.[letter], 폭 } };
}
}
return Math.max(MIN_WIDTH, Math.ceil(widest + PADDING));
}
/** 폭을 자동 맞춤 값으로 바꿈(열넓이 명령 하나). */
@@ -12,6 +12,7 @@
import "./spreadsheet_stage3.css"; // sub4 패널 스타일(잇는 쪽이 붙임)
import { createMapContextMenu, type MapContextMenuItem } from "@ui/ui_template_context_menu";
import { el, showToast } from "@ui/ui_template_elements";
import { createFloatPanel } from "@ui/ui_template_float_panel";
import { parseRange, rangeToA1, toA1 } from "./spreadsheet_address";
import { autoSumCells, autoSumRange } from "./spreadsheet_autosum";
import { isError } from "./spreadsheet_eval";
@@ -40,7 +41,6 @@ const TEXT = {
validation: "데이터 유효성…",
names: "이름 관리…",
grid: "눈금선 보이기 · 숨기기",
close: "닫기",
list: "값 목록(Alt+↓)",
sheets: "시트 숨기기 · 탭 색…",
audit: "수식 분석",
@@ -121,18 +121,14 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
};
// ── 패널 창(조건부 서식 · 유효성 · 이름) — 한 번에 하나 ─────────────────────
const title = el("span", { className: "ss-dialog__title" });
const body = el("div", { className: "ss-dialog__body" });
const shut = el("button", {
className: "ss-find__btn",
text: "×",
attrs: { type: "button", title: TEXT.close },
});
const dialog = el("div", {
className: "ss-dialog ui-popup",
attrs: { hidden: "" },
children: [el("div", { className: "ss-dialog__head", children: [title, shut] }), body],
// 떠 있는 판 부품(머리 · 닫기 ✕ · 몸 · 553-6) — 자리는 `.ss-dialog`
const { root: dialog, body } = createFloatPanel({
title: " ",
onClose: () => (closePanel(), back()),
});
dialog.classList.add("ss-dialog");
dialog.setAttribute("hidden", "");
const title = dialog.querySelector(".ui-float-panel__title") as HTMLElement;
ctx.root.append(dialog);
let panel: PartHandle | null = null;
const closePanel = (): void => {
@@ -141,7 +137,6 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
body.replaceChildren();
dialog.setAttribute("hidden", "");
};
shut.addEventListener("click", () => (closePanel(), back()));
dialog.addEventListener("keydown", (e) => {
if (e.key !== "Escape") return;
e.stopPropagation();
@@ -11,7 +11,7 @@
* 시트 전체를 훑지 말고 그리는 칸만 물음(값 하나 · 칸 하나 읽는 값싼 부름).
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import { createIconButton, el } from "@ui/ui_template_elements";
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { toA1 } from "./spreadsheet_address";
import { isError } from "./spreadsheet_eval";
@@ -137,11 +137,9 @@ export function attachErrorTip(ctx: SpreadsheetContext, trace?: TraceHandle): Er
const g = ctx.grid.root.getBoundingClientRect();
layer.style.left = `${box.x - (a.left - g.left) - 20}px`;
layer.style.top = `${box.y - (a.top - g.top)}px`;
const btn = el("button", {
className: "ss-errtip__btn",
text: "⚠",
attrs: { type: "button", "aria-label": w.까닭 },
});
const btn = createIconButton({ glyph: "⚠", title: w.까닭, variant: "outline", size: 18 });
btn.classList.add("ss-errtip__btn");
btn.removeAttribute("title"); // 까닭은 옆 풍선이 보임
const balloon = el("div", { className: "ss-errtip__balloon", text: w.까닭 });
btn.addEventListener("mousedown", (ev) => ev.stopPropagation());
btn.addEventListener("click", () => (menu ? closeMenu() : openMenu(w, r, c)));
+3 -5
View File
@@ -6,7 +6,7 @@
* 빨간 세모는 C 격자 · 우클릭 [메모 …] 는 E 메뉴 몫(브레인 배정).
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import { createButton, el } from "@ui/ui_template_elements";
import { watchClose } from "./spreadsheet_actions";
import { parseClipboard } from "./spreadsheet_clipboard";
import { attachComments, getComment, setComment } from "./spreadsheet_comments";
@@ -138,10 +138,8 @@ export function attachExtras(
: undefined;
picker.replaceChildren(
...(Object.keys(TEXT.modes) as PasteSpecialMode[]).map((mode) => {
const b = el("button", {
text: TEXT.modes[mode],
attrs: { type: "button", "data-mode": mode },
});
const b = createButton({ label: TEXT.modes[mode], variant: "ghost", size: 22 });
b.dataset.mode = mode;
b.addEventListener("click", () => {
picker.setAttribute("hidden", "");
// 늦게 — 크기 예산
@@ -4,7 +4,8 @@
* Enter = 다음 · Esc = 닫고 격자로. 바꾸기 = 활성 칸이 맞으면 바꾸고 다음으로(엑셀).
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import { createButton, createIconButton, el, setUiSize } from "@ui/ui_template_elements";
import { createFloatPanel } from "@ui/ui_template_float_panel";
import { findAll, findNext, replaceAll, replaceOne, type FindOptions } from "./spreadsheet_find";
import { selectCell } from "./spreadsheet_selection";
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
@@ -29,12 +30,14 @@ export interface FindPanelHandle extends PartHandle {
}
export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindPanelHandle {
const query = el("input", { className: "ss-find__input", attrs: { placeholder: TEXT.find } });
const repl = el("input", { className: "ss-find__input", attrs: { placeholder: TEXT.replace } });
const field = (placeholder: string): HTMLInputElement =>
setUiSize(el("input", { className: "ui-input ss-find__input", attrs: { placeholder } }), 24);
const query = field(TEXT.find);
const repl = field(TEXT.replace);
const status = el("span", { className: "ss-find__status" });
const button = (text: string, onClick: () => void): HTMLButtonElement => {
const b = el("button", { className: "ss-find__btn", text, attrs: { type: "button" } });
b.addEventListener("click", onClick);
const b = createButton({ label: text, variant: "ghost", size: 22, onClick });
b.classList.add("ss-find__btn");
return b;
};
const check = (text: string): HTMLInputElement => {
@@ -42,8 +45,8 @@ export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindP
options.append(el("label", { className: "ss-find__opt", children: [box, text] }));
return box;
};
const closeBtn = button("×", close);
closeBtn.title = TEXT.close;
const closeBtn = createIconButton({ glyph: "×", title: TEXT.close, size: 22, onClick: close });
closeBtn.classList.add("ss-find__btn");
const options = el("div", { className: "ss-find__row" });
const caseBox = check(TEXT.case);
const wholeBox = check(TEXT.whole);
@@ -52,19 +55,19 @@ export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindP
className: "ss-find__row",
children: [repl, button(TEXT.one, one), button(TEXT.all, all)],
});
const root = el("div", {
className: "ss-find",
attrs: { hidden: "" },
children: [
el("div", {
className: "ss-find__row",
children: [query, button(TEXT.next, next), closeBtn],
}),
replaceRow,
options,
status,
],
});
// 떠 있는 판 부품(머리 없음 · 553-6) — 자리는 `.ss-find`
const { root, body } = createFloatPanel({ popover: true });
root.classList.add("ss-find");
root.setAttribute("hidden", "");
body.append(
el("div", {
className: "ss-find__row",
children: [query, button(TEXT.next, next), closeBtn],
}),
replaceRow,
options,
status,
);
ctx.root.append(root);
const opts = (): FindOptions => ({
@@ -1,50 +1,9 @@
/* =============================================================================
* spreadsheet_format_dialog.css (2단계 · 주인 sub_laptop_1)
* 셀 서식 대화상자 — 뿌리 배경(`.ui-confirm`)은 공용 확인 모달과 같은 것을 그대로 씀.
* 셀 서식 대화상자 — 창 · 탭 · 칸 = 부품 모달(40rem) · 탭(32) · 입력 · 고르기(28) · 여기는 안 배치만(553-6).
* 색 · 간격은 테마 토큰만(`ui_template_theme.css`).
* ========================================================================== */
.ss-fmtdlg__panel {
display: flex;
flex-direction: column;
width: min(560px, 92vw);
max-height: 84vh;
padding: var(--spacing-16);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-fmtdlg__title {
margin: 0 0 var(--spacing-8);
font-size: 1rem;
color: var(--color-text-body);
}
.ss-fmtdlg__tabs {
display: flex;
flex-wrap: wrap;
gap: 2px;
border-bottom: 1px solid var(--color-border);
}
.ss-fmtdlg__tabbtn {
padding: 6px 12px;
border: 1px solid var(--color-border);
border-bottom: 0;
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
background: var(--color-paper);
color: var(--color-text-secondary);
font-size: 0.82rem;
cursor: pointer;
}
.ss-fmtdlg__tabbtn.is-active {
background: var(--color-canvas);
color: var(--color-text-body);
font-weight: 600;
}
.ss-fmtdlg__body {
flex: 1;
min-height: 300px;
@@ -68,21 +27,7 @@
width: 140px;
flex-shrink: 0;
color: var(--color-text-secondary);
font-size: 0.82rem;
}
.ss-fmtdlg__row select,
.ss-fmtdlg__row input[type="text"],
.ss-fmtdlg__row input:not([type]),
.ss-fmtdlg__row input[type="number"],
.ss-fmtdlg__code {
height: 28px;
padding: 0 6px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-canvas);
color: var(--color-text-body);
font-size: 0.82rem;
font-size: var(--text-body-sm);
}
.ss-fmtdlg__code {
@@ -135,7 +80,7 @@
.ss-fmtdlg__note {
margin: 0 0 var(--spacing-8);
color: var(--color-text-muted);
font-size: 0.8rem;
font-size: var(--text-caption);
}
.ss-fmtdlg__actions {
@@ -12,7 +12,9 @@
* 우클릭 메뉴 [셀 서식] 도 같은 함수를 부르면 됨(`spreadsheet_menu.ts`).
* ========================================================================== */
import { createButton, el } from "@ui/ui_template_elements";
import { createButton, el, setUiSize } from "@ui/ui_template_elements";
import { createTabs } from "@ui/ui_template_tabs";
import { openModal } from "@ui/ui_template_modal";
import { toFrac } from "@ui/sheet/ui_template_sheet_frac";
import { colName, rangeToA1, toA1 } from "./spreadsheet_address";
import {
@@ -39,6 +41,7 @@ const TEXT = {
tabProtect: "보호",
ok: "확인",
cancel: "취소",
close: "닫기",
category: "범주",
decimals: "소수 자릿수",
thousands: "천 단위 구분 기호 사용",
@@ -105,17 +108,9 @@ export function openFormatDialog(ctx: SpreadsheetContext): void {
const spansMany = range.r0 !== range.r1 || range.c0 !== range.c1;
let mergeWanted = (ctx.sheet().병합 ?? []).includes(rangeToA1(range));
const overlay = el("div", { className: "ui-confirm ss-fmtdlg" });
const panel = el("div", { className: "ss-fmtdlg__panel" });
const tabsBar = el("div", { className: "ss-fmtdlg__tabs" });
const body = el("div", { className: "ss-fmtdlg__body" });
overlay.append(panel);
panel.append(el("h2", { className: "ss-fmtdlg__title", text: TEXT.title }), tabsBar, body);
const close = (): void => overlay.remove();
overlay.addEventListener("click", (ev) => {
if (ev.target === overlay) close();
});
let close = (): void => {};
// ── 표시 형식 ────────────────────────────────────────────────────────
let category: Category = guessCategory(draft.형식);
@@ -464,22 +459,25 @@ export function openFormatDialog(ctx: SpreadsheetContext): void {
[TEXT.tabProtect, protectTab],
];
function showTab(index: number): void {
body.replaceChildren(tabs[index][1]());
tabsBar.querySelectorAll(".ss-fmtdlg__tabbtn").forEach((btn, i) => {
btn.classList.toggle("is-active", i === index);
});
}
tabs.forEach(([label], i) => {
const btn = el("button", {
className: "ss-fmtdlg__tabbtn",
text: label,
attrs: { type: "button" },
});
btn.addEventListener("click", () => showTab(i));
tabsBar.append(btn);
const tabBar = createTabs({
tabs: tabs.map(([label], i) => ({ key: String(i), label })),
value: "0",
size: 32,
onChange: (key) => showTab(Number(key)),
});
tabsBar.append(tabBar.root);
function showTab(index: number): void {
const tab = tabs[index][1]();
// 칸 = 부품 입력 · 고르기 28(553-6)
const fields = "select, input:not([type]), input[type=text], input[type=number]";
for (const f of tab.querySelectorAll<HTMLElement>(fields)) {
f.classList.add(f.tagName === "SELECT" ? "ui-select" : "ui-input");
setUiSize(f, 28);
}
body.replaceChildren(tab);
tabBar.select(String(index));
}
function apply(): void {
ctx.dispatch({ 종류: "서식", 시트: ctx.selection.시트, 범위: ctx.selection.범위, 바꿀: draft });
@@ -497,13 +495,21 @@ export function openFormatDialog(ctx: SpreadsheetContext): void {
const actions = el("div", {
className: "ss-fmtdlg__actions",
children: [
createButton({ label: TEXT.cancel, variant: "ghost", onClick: close }),
createButton({ label: TEXT.cancel, variant: "ghost", onClick: () => close() }),
createButton({ label: TEXT.ok, variant: "filled", onClick: apply }),
],
});
panel.append(actions);
showTab(0);
document.body.append(overlay);
dialogKeys(overlay, close, apply);
openModal({
title: TEXT.title,
closeLabel: TEXT.close,
width: "40rem",
dialogClass: "ss-fmtdlg",
mount: (box, shut) => {
close = shut;
box.append(tabsBar, body, actions);
dialogKeys(box.parentElement ?? box, shut, apply);
},
});
}
@@ -276,3 +276,40 @@ html[data-touch="on"] .aislo-grid__fill-handle {
touch-action: pan-x pan-y;
}
}
/* 칸 칠(옵션 `칠` · 요구 621-5) — 칸 원래 채움(인라인 background-color) 위에 겹침 · 어두움은 --color-* 가 따라감. */
.aislo-cell[data-tone~="success"] {
background-image: linear-gradient(
color-mix(in srgb, var(--color-success) 18%, transparent),
color-mix(in srgb, var(--color-success) 18%, transparent)
);
}
.aislo-cell[data-tone~="warning"] {
background-image: linear-gradient(
color-mix(in srgb, var(--color-warning) 18%, transparent),
color-mix(in srgb, var(--color-warning) 18%, transparent)
);
}
.aislo-cell[data-tone~="info"] {
background-image: linear-gradient(
color-mix(in srgb, var(--color-info) 18%, transparent),
color-mix(in srgb, var(--color-info) 18%, transparent)
);
}
.aislo-cell[data-tone~="danger"] {
background-image: linear-gradient(
color-mix(in srgb, var(--color-danger) 18%, transparent),
color-mix(in srgb, var(--color-danger) 18%, transparent)
);
}
.aislo-cell[data-tone~="muted"] {
color: var(--color-text-muted) !important;
}
.aislo-cell[data-tone~="mine"] {
box-shadow: inset 3px 0 0 var(--color-accent);
}
/* 읽기만 — 시트 탭 「+」 숨김 */
.ss--readonly .ss-tabs__add {
display: none;
}
+36 -2
View File
@@ -437,6 +437,16 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle {
};
}
/** 굴리는 영역 밖 테두리 자르기(`overlay` 좌표) — 고정 머리 칸 · 모서리 · 스크롤바 위로 안 나가게(요구 566). */
function clipOf(b: CellBox, inFrozenCol: boolean, inFrozenRow: boolean): string {
const pad = (n: number): number => Math.max(0, Math.ceil(n));
const top = inFrozenRow ? 0 : pad(frozenH - b.y);
const left = inFrozenCol ? 0 : pad(frozenW - b.x);
const bottom = pad(b.y + b.h - (frozenH + paneBR.clientHeight));
const right = pad(b.x + b.w - (frozenW + paneBR.clientWidth));
return `inset(${top}px ${right}px ${bottom}px ${left}px)`;
}
function renderSelection(): void {
let selLayer = overlay.querySelector<HTMLElement>(".aislo-grid__sel-layer");
if (!selLayer) {
@@ -470,6 +480,7 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle {
node.style.height = `${screen.h}px`;
node.style.borderColor = "var(--aislo-grid-selected)";
node.style.borderWidth = i === 0 ? "2px" : "1px";
node.style.clipPath = clipOf(screen, inFrozenCol, inFrozenRow);
selLayer!.append(node);
}
// 활성 칸 테두리 — 칸 다시 그리기(`renderRowOfCells`) 없이 고름만 바뀌어도 옛 칸에 남지 않게.
@@ -487,6 +498,17 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle {
handle.className = "aislo-grid__fill-handle";
handle.style.left = `${corner.x + corner.w - 4}px`;
handle.style.top = `${corner.y + corner.h - 4}px`;
const hx = corner.x + corner.w;
const hy = corner.y + corner.h;
const hFC = handleAt.c < frozenCols;
const hFR = handleAt.r < frozenRows;
if (
(!hFC && hx < frozenW) ||
(!hFR && hy < frozenH) ||
hx > frozenW + paneBR.clientWidth ||
hy > frozenH + paneBR.clientHeight
)
handle.style.display = "none";
selLayer!.append(handle);
}
@@ -525,6 +547,16 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle {
node.style.width = `${merged.w}px`;
node.style.height = `${merged.h}px`;
node.style.borderColor = REF_COLORS[ref.색번호 % REF_COLORS.length];
node.style.clipPath = clipOf(
{
x: parseFloat(node.style.left),
y: parseFloat(node.style.top),
w: merged.w,
h: merged.h,
},
inFrozenCol,
inFrozenRow,
);
refLayer!.append(node);
}
}
@@ -540,8 +572,10 @@ export function createGrid(ctx: SpreadsheetContext): GridHandle {
const contentY =
y < headRowH() ? -1 : y - headRowH() + (y - headRowH() > frozenH ? paneBR.scrollTop : 0);
if (y < headRowH()) {
const c = colGeo.indexAt(contentX);
const edge = Math.abs(colGeo.offset(c + 1) - contentX) <= RESIZE_HOTZONE;
const at = colGeo.indexAt(contentX);
const after = at > 0 && contentX - colGeo.offset(at) <= RESIZE_HOTZONE; // 경계 오른쪽 반 = 앞 열 경계
const c = after ? at - 1 : at;
const edge = after || Math.abs(colGeo.offset(c + 1) - contentX) <= RESIZE_HOTZONE;
return { kind: edge ? "colEdge" : "colHead", r: 0, c };
}
if (x < headColW()) {
@@ -319,6 +319,9 @@ export function renderCells(
if (need > w * 0.9) longs.push({ r: r0, c: c0, el: cellEl, own: w, need, align });
}
}
const tone = ctx.fill?.(sheet.id, r0, c0);
if (tone) cellEl.dataset.tone = tone;
else delete cellEl.dataset.tone; // 재사용 div — 앞 칸 꼴을 지움
cellEl.classList.toggle("is-error", display.isError);
cellEl.classList.toggle("has-comment", !!comment);
cellEl.classList.toggle(
+2 -11
View File
@@ -120,20 +120,11 @@
width: 320px;
padding: var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-links__search {
padding: 4px 6px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-canvas);
color: var(--color-text-body);
font: inherit;
}
.ss-links__list {
display: flex;
flex-direction: column;
@@ -155,7 +146,7 @@
}
.ss-links__row:hover {
background: var(--color-mist-violet);
background: var(--color-hover);
}
.ss-links__note {
+2 -2
View File
@@ -585,8 +585,8 @@ export function mountLinks(
function openPick(anchor: HTMLElement): void {
closePick();
const input = el("input", {
className: "ss-links__search",
attrs: { type: "search", placeholder: TEXT.search },
className: "ui-input ss-links__search",
attrs: { type: "search", placeholder: TEXT.search, "data-size": "28" },
}) as HTMLInputElement;
const list = el("div", { className: "ss-links__list" });
const note = (text: string) => el("div", { className: "ss-links__note", text });
+4 -4
View File
@@ -59,15 +59,15 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
/** 메모 글 입력 — 작은 오버레이(native prompt 안 씀, 자동화 창이 native 창을 자동 취소하는
* 문제는 `showConfirmDialog` 머리 주석과 같은 까닭). 저장을 누르면 `onSave`. */
function openCommentEditor(initial: string, onSave: (text: string) => void): void {
const overlay = el("div", { className: "ss-comment-editor" });
const panel = el("div", { className: "ss-comment-editor__panel" });
const overlay = el("div", { className: "ui-confirm ss-comment-editor" });
const panel = el("div", { className: "ui-confirm__panel" });
const textarea = el("textarea", {
className: "ss-comment-editor__input",
className: "ui-input ss-comment-editor__input",
}) as HTMLTextAreaElement;
textarea.value = initial;
const done = (): void => overlay.remove();
const actions = el("div", {
className: "ss-comment-editor__actions",
className: "ui-confirm__actions",
children: [
createButton({ label: "취소", variant: "ghost", onClick: done }),
createButton({
+2 -2
View File
@@ -233,9 +233,9 @@ export function attachMouse(
}
function onDbl(e: MouseEvent): void {
if (e.target === ed.ta || ctx.readOnly) return;
if (e.target === ed.ta) return;
const hit = ctx.grid.hitTest(e.clientX, e.clientY);
if (!hit) return;
if (!hit || (ctx.readOnly && hit.kind !== "colEdge")) return; // 읽기 전용 = 열 경계 맞춤만
if (hit.kind === "fillHandle") return fillDown();
if (hit.kind === "colEdge") {
// 고른 열 전체의 경계면 고른 열 모두(엑셀)
@@ -100,12 +100,17 @@ export function openSortDialog(ctx: SpreadsheetContext): () => void {
function drawKeys() {
list.replaceChildren(
...keys.map((k, i) => {
const col = el("select", { attrs: { "aria-label": t("Column") } });
const col = el("select", {
className: "ui-select",
attrs: { "aria-label": t("Column"), "data-size": "28" },
});
for (let c = rg.c0; c <= rg.c1; c++)
col.append(el("option", { text: columnLabel(c), attrs: { value: String(c) } }));
col.value = String(k.열);
col.addEventListener("change", () => (k.열 = Number(col.value)));
const dir = el("select", {
className: "ui-select",
attrs: { "data-size": "28" },
children: [
el("option", { text: t("Asc"), attrs: { value: "asc" } }),
el("option", { text: t("Desc"), attrs: { value: "desc" } }),
@@ -7,7 +7,7 @@
display: flex;
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 18%);
background: var(--color-scrim);
}
.ss-sort__box,
@@ -48,11 +48,6 @@
.ss-sort__row select {
flex: 1;
padding: var(--spacing-4);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-canvas);
color: inherit;
}
.ss-sort__row option {
@@ -92,8 +87,8 @@
}
.ss-filter__btn.is-on {
color: var(--color-royal-amethyst);
border-color: var(--color-royal-amethyst);
color: var(--color-accent);
border-color: var(--color-accent);
}
.ss-filter__panel {
@@ -130,5 +125,5 @@
}
.ss-filter__link:hover {
background: var(--color-mist-violet);
background: var(--color-hover);
}

Some files were not shown because too many files have changed in this diff Show More