1506 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
12661 changed files with 2560472 additions and 108211 deletions
+4 -1
View File
@@ -89,4 +89,7 @@ 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=
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/
+12 -3
View File
@@ -133,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;
}
@@ -178,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)); // ☰ 에 포커스가 있어도 닫힘
@@ -485,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;
-4
View File
@@ -477,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;
+3 -2
View File
@@ -186,6 +186,8 @@ export const STATE_REGISTRY = {
"b08-milestone": { bucket: "draft", scope: "project" },
/** 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).
@@ -200,8 +202,7 @@ export const STATE_REGISTRY = {
"route-draft-hold": { bucket: "draft", scope: "project" },
/** D04 연도별 지정 업체 목록(531-3) — [저장] 때 PUT plan/assignees. */
"d04-assignees": { bucket: "draft", scope: "project" },
/* D02 · E 페이지 캐시(401) — 진입 · 떠남 비우기가 같은 표를 봄 · 옛 키는 한 번 옮김. */
"d02-points": { bucket: "draft", scope: "project" },
/* E 페이지 캐시(401) — 진입 · 떠남 비우기가 같은 표를 봄 · 옛 키는 한 번 옮김. */
"e02-ground": { bucket: "draft", scope: "project" },
"e03-prob": { bucket: "draft", scope: "project" },
"e04-settings": { bucket: "draft", scope: "project" },
+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;
}
+18
View File
@@ -52,6 +52,22 @@ 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;
routeId: () => number | null;
@@ -249,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();
-1
View File
@@ -546,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),
});
@@ -74,10 +74,7 @@ export function createViewer3dModal(deps: Viewer3dModalDeps) {
}
// 평면 도구줄 아이콘 단추(568-2) — 클래스는 가이드 촬영이 잡음.
const button = mapToolButton("view3d", {
title: "3D 화면을 큰 창으로 엽니다 — 닫으면 그리기를 멈춥니다",
onClick: open,
});
const button = mapToolButton("view3d", { onClick: open }); // 마우스 글 = 공용 표(634-2)
button.classList.add("b05-route__3d-open");
return {
@@ -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];
+2 -2
View File
@@ -28,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,
+9 -8
View File
@@ -20,7 +20,6 @@ export type KeyScope =
| "drainage"
| "section"
| "plan"
| "d02"
| "b04"
| "cad"
| "b08"
@@ -48,8 +47,7 @@ export const KEY_SCOPES: Record<KeyScope, { label: Text; page: string }> = {
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 route analysis"], page: "plan" },
d02: { label: ["D02 기준지정", "D02 base points"], page: "d02" },
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" },
@@ -140,17 +138,20 @@ export const KEY_ACTIONS: readonly KeyAction[] = [
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.delete", "plan", ["Delete"], ["고른 것 지움", "Delete selection"]),
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.vertex", "plan", ["고른 선 누름", "꼭짓점 끌기"], ["꼭짓점 넣고 끌기 · 옮김", "Insert · drag vertex"], F),
A("plan.unvertex", "plan", ["꼭짓점 · 마커 오른쪽 누름"], ["지움", "Delete"], 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("d02.place", "d02", ["지도 누름"], ["지점 놓기", "Place point"], F),
A("d02.drag", "d02", ["표식 끌기"], ["지점 옮김", "Move point"], 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),
+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> {
+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 -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)
+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));
}
/** 폭을 자동 맞춤 값으로 바꿈(열넓이 명령 하나). */
@@ -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 -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") {
// 고른 열 전체의 경계면 고른 열 모두(엑셀)
@@ -94,6 +94,8 @@ export interface SpreadsheetContext {
/** 지금 문서 — 명령만이 바꿈(직접 고치지 말 것) */
book: Workbook;
readOnly: boolean;
/** 칸 칠(옵션 `칠`) — 시트 id · 행 · 열 → 꼴 이름(공백으로 여럿 · 없으면 칠 없음) · 보이는 칸만 부름 */
fill?: (시트: string, r: number, c: number) => string | null | undefined;
engine: CalcEngine;
history: History;
selection: Selection;
+1 -1
View File
@@ -24,7 +24,7 @@
2. 시작 연도와 사업 지역을 넣음. [사업 연도 · 지역](D01/02_사업연도_지역) 참고.
3. 프로젝트명을 확인함. [프로젝트명](D01/03_프로젝트명) 참고.
4. [등록]을 누름. 문제가 있는 칸은 아래에 빨간 안내가 나옴. [입력 오류와 등록](D01/04_입력오류와_등록) 참고.
5. 등록되면 바로 다음 화면인 [기준 지정](D02/00_설명서)이 열림.
5. 등록되면 바로 다음 화면인 [노선 분석](D03/00_설명서)이 열리고 기준점 둘레 1 km 지형을 받음.
> 주의: 등록 직후에는 시점 · 종점이 비어 있음. 두 점을 정하고 저장해야 노선 분석 단계로 갈 수 있음.
@@ -23,4 +23,4 @@
- 카드 넷 = 예정노선 · 임도설계 · 사방설계 · 안정성평가. 사방설계는 「준비 중」 표시가 붙어 눌러도 열리지 않음.
- 산림청이 아닌 계정은 「예정노선」 카드가 흐리게 보임. 마우스를 올리면 까닭이 말풍선으로 보이고, 눌러도 「예정노선은 산림청(시스템 관리자 · go.kr 메일)만 만들 수 있습니다.」 안내만 나옴.
> 참고: 이미 만든 예정노선은 대시보드 프로젝트 표에 「예정노선」 종류로 나옴. 표의 단계 막대(기준 지정 · 노선 분석 · 확정 · 내보내기)를 누르면 그 단계 화면으로 감.
> 참고: 이미 만든 예정노선은 대시보드 프로젝트 표에 「예정노선」 종류로 나옴. 표의 단계 막대(노선 분석 · 확정 · 내보내기)를 누르면 그 단계 화면으로 감.
@@ -12,7 +12,7 @@
| --- | --- |
| 1 | 칸을 모두 채운 뒤 [등록]을 누름. |
| 2 | 문제가 있으면 그 칸 아래에 빨간 안내가 나오고 등록되지 않음. 고친 뒤 다시 [등록]. |
| 3 | 문제가 없으면 프로젝트가 만들어지고 「프로젝트를 만들었습니다.」 알림과 함께 [기준 지정](D02/00_설명서) 화면이 열림. |
| 3 | 문제가 없으면 프로젝트가 만들어지고 「프로젝트를 만들었습니다.」 알림과 함께 [노선 분석](D03/00_설명서) 화면이 열림. |
| 4 | 등록하지 않고 나가려면 [취소]를 누름. 대시보드로 돌아감. |
![입력 오류 안내](D01_04_errors.webp)
+1 -1
View File
@@ -17,7 +17,7 @@
| 사업 연도 끝 | [노선 분석](D03/00_설명서)에서 구역마다 연도를 고르고 [저장]하면 가장 늦은 연도로 정해짐 |
| 임도 종류 · 설계속도 | 받지 않음. [확정 · 내보내기](D04/00_설명서) 뒤 업체가 임도 프로젝트로 연결하면 임도설계 기본값(작업임도 · 20 km/h)으로 시작하고, 임도 프로젝트에서 바꿈 |
| 노선 시작 · 종료 측점 | 받지 않음. 측점은 선마다 No.0 부터 새로 셈 |
| 시점 · 종점 | [기준 지정](D02/00_설명서)에서 지도로 정함 |
| 지점 | [노선 분석](D03/00_설명서) 지도 우클릭 「지점 놓기」 |
## 프로젝트명 자동 조합
-34
View File
@@ -1,34 +0,0 @@
# 기준 지정
예정노선 1단계. 사업 전체의 시점과 종점, 두 점을 지도 위에 정하는 화면. 연도는 나누지 않음.
## 여는 길
- [예정노선 등록](D01/00_설명서)에서 [등록]을 누르면 바로 열림.
- 대시보드 프로젝트 표에서 예정노선 줄의 단계 막대 「기준 지정」을 눌러도 열림.
- 이미 만든 예정노선은 어느 단계 화면에서든 ☰ 에서 「기준 지정」을 골라 돌아옴. [단계 이동 메뉴](D02/06_단계_이동_메뉴) 참고.
- 두 점 저장은 산림청만 할 수 있음. 지정받은 다른 회사는 보기만 가능함.
![화면 한눈에](D02_00_overview.webp)
## 화면 구역
| 번호 | 구역 | 하는 일 |
| --- | --- | --- |
| 1 | 「전체 시점 · 종점」 카드 | 시점 · 종점 두 줄. 📍 · 경도 · 위도 칸이 있음 |
| 2 | [일반] [위성] 단추 | 바탕 지도를 바꿈. 고른 값은 기억됨 |
| 3 | 지도 | 시점 · 종점 표지와 둘을 잇는 선이 보임. 지도를 눌러 점을 놓고, 표지를 끌어 옮김 |
| 4 | 단추 줄 | [?] 가이드 · [<] 이전 · [>] 다음 · [저장] |
## 작업 순서
1. 화면이 열리면 시점과 종점 두 줄이 있음. 비어 있는 첫 줄이 놓을 점으로 강조됨. [시점 · 종점 표](D02/01_시점_종점_표) 참고.
2. 지도에서 시점이 될 자리를 누름. 다음으로 종점 자리를 누름. [지도에서 지점 놓기](D02/02_지도에서_지점_놓기) 참고.
3. 놓은 뒤 고치려면 표지를 끌어 옮기거나 경도 · 위도 칸에 값을 직접 넣음. [좌표 직접 입력](D02/03_좌표_직접_입력) 참고.
4. 지형이 헷갈리면 [위성]으로 바꿔 봄. [일반 · 위성 지도](D02/04_일반_위성) 참고.
5. [저장] 또는 [>]를 누름. [단추와 저장](D02/05_단추와_저장) 참고.
6. 노선 분석 화면이 열리면 이 두 점 사이를 직접 그어 감. [노선 분석](D03/00_설명서) 참고.
> 주의: 서버에는 [저장]이나 [>]를 눌러 저장하거나, 다른 단계로 옮겨 갈 때 저장되어야 반영됨. 화면 안에서 점을 놓기만 해서는 서버에 남지 않음.
> 참고: 연도는 이 화면에서 나누지 않음. 노선 분석에서 그은 선에 구역을 나누고 구역마다 연도를 고름.
@@ -1,32 +0,0 @@
# 시점 · 종점 표
왼쪽 패널의 「전체 시점 · 종점」 카드. 점마다 줄 하나로 이름 · 📍 · 경도 · 위도를 보여 줌.
## 여는 길
- 기준 지정 화면 왼쪽 패널 맨 위의 「전체 시점 · 종점」 카드. 제목 오른쪽 ▼ 를 누르면 접고 펼침.
![시점 · 종점 표](D02_01_points_table.webp)
그림 번호 — 1 안내 글 · 2 시점 줄 · 3 📍 단추 · 4 경도 칸 · 5 위도 칸 · 6 저장 좌표계 줄
## 단계
| 단계 | 할 일 |
| --- | --- |
| 1 | 두 줄 중 「시점」이 노선 전체의 출발점, 「종점」이 도착점. |
| 2 | 줄 왼쪽 띠로 지금 놓을 점인지 확인. 📍 를 누른 줄이 강조됨. |
| 3 | 값을 고치는 방법은 [지도에서 지점 놓기](D02/02_지도에서_지점_놓기) · [좌표 직접 입력](D02/03_좌표_직접_입력) 참고. |
## 보이는 것
| 줄 이름 | 뜻 |
| --- | --- |
| 시점 | 노선 전체의 출발점. 노선 분석에서 첫 점을 이 자리에서 시작함 |
| 종점 | 노선 전체의 도착점. 노선 분석에서 이 점 가까이 가면 선이 종점에 붙음 |
- 표 아래 한 줄 = 저장 상태. 저장 전이면 「아직 저장 안 함」, 저장했으면 「저장 좌표계 EPSG:번호」와 시점의 X · Y(m).
- 산림청이 아닌 계정은 📍 단추가 안 보이고 칸이 잠겨 있음. 보기만 가능.
- 옛 프로젝트처럼 두 점이 비어 있어도 연도별 지점이 있었다면 첫 연도 시점과 마지막 연도 종점을 이어 두 점으로 접어 보여 줌.
> 참고: 칸마다 뜻과 규칙은 [칸 뜻](D02/10_칸_뜻)에 있음. 좌표계가 정해지는 방법은 [쓰인 방법과 근거](D02/20_쓰인_방법과_근거) 참고.
@@ -1,39 +0,0 @@
# 지도에서 지점 놓기
지도를 눌러 시점 · 종점을 놓고, 놓은 표지를 끌어 옮기는 방법.
## 여는 길
- 기준 지정 화면 가운데 지도와 왼쪽 「전체 시점 · 종점」 카드의 📍 단추.
![시점과 종점을 이은 지도](D02_02_map.webp)
그림 번호 — 1 지도 위 안내 글
## 단계
| 단계 | 할 일 |
| --- | --- |
| 1 | 놓을 점을 고름. 화면이 열리면 비어 있는 첫 줄이 자동으로 골라져 강조됨. 다른 줄에 놓으려면 그 줄의 📍 를 누름. |
| 2 | 지도에서 놓을 자리를 누름. 그 자리에 표지가 놓이고 표의 경도 · 위도가 채워짐. |
| 3 | 놓고 나면 다음 빈 점이 자동으로 놓을 점이 됨. |
| 4 | 놓은 뒤 고치려면 표지를 마우스로 끌어 옮김. 표의 값이 따라 바뀜. |
| 5 | 지도는 빈 곳을 끌어 옮기고 휠로 확대 · 축소함. |
![놓을 지점으로 고른 줄](D02_03_pick_point.webp)
그림 번호 — 1 📍 단추 · 2 강조된 줄(놓을 점)
![점을 고르지 않고 지도를 누른 모습](D02_04_map_toast.webp)
그림 번호 — 1 「지도에 놓을 지점 고르기」 안내
## 보이는 것
- 두 점이 모두 놓이면 둘 사이에 선이 이어짐. 표지 위에는 「시점」 · 「종점」 이름이 붙음.
- 놓을 점을 고르지 않고 지도를 누르면 「지도에 놓을 지점 고르기」 안내가 나오고 아무것도 놓이지 않음.
- 점이 하나도 없으면 우리나라 가운데쯤이 먼저 보임. 처음 점을 놓으면 그 점에 맞춰 보이고, 그 뒤로는 사용자가 맞춘 보기(중심 · 확대)를 그대로 둠.
- 지도는 카카오맵을 씀.
- 산림청이 아닌 계정은 지도를 눌러도 놓이지 않고 표지도 끌 수 없음. 지도 위 안내 글이 「보기 전용 — 지점은 산림청만 고칩니다.」로 바뀜.
> 주의: 놓은 점은 [저장]이나 [>]를 누르거나 다른 단계로 옮겨 가기 전까지 서버에 남지 않음. [단추와 저장](D02/05_단추와_저장) 참고.
@@ -1,28 +0,0 @@
# 좌표 직접 입력
다른 프로그램에서 구한 좌표를 표의 경도 · 위도 칸에 직접 넣는 방법.
## 여는 길
- 왼쪽 「전체 시점 · 종점」 카드의 줄마다 있는 경도 · 위도 두 칸.
## 단계
| 단계 | 할 일 |
| --- | --- |
| 1 | 고칠 줄의 경도 칸에 값을 넣음. |
| 2 | 위도 칸에도 값을 넣고 칸 밖을 누르거나 Tab 키를 누름. |
| 3 | 두 값이 범위 안이면 점이 옮겨지고 지도 표지가 따라 움직임. |
| 4 | 범위 밖이면 두 칸이 빨갛게 표시되고 반영되지 않음. 값을 고쳐 다시 넣음. |
![범위 밖 값 안내](D02_05_coordinate_error.webp)
그림 번호 — 1 경도 칸(범위 밖 값 200을 넣은 모습) · 2 위도 칸
## 보이는 것
- 경도는 120 ~ 135, 위도는 30 ~ 45 범위만 받음.
- 두 칸이 모두 범위 안일 때 반영됨. 한 칸만 넣으면 반영되지 않음.
- 반영될 때 소수 7 자리(약 1 cm)로 반올림됨.
> 주의: 좌표는 경도 · 위도(WGS84)로 넣음. 평면좌표(X · Y)를 그대로 넣으면 범위 밖으로 처리됨.
@@ -1,27 +0,0 @@
# 일반 · 위성 지도
지도 오른쪽 위 [일반] [위성] 단추로 바탕 지도를 바꾸는 방법.
## 여는 길
- 기준 지정 화면 지도 오른쪽 위 두 칸짜리 단추.
![일반 · 위성 단추](D02_08_map_type.webp)
그림 번호 — 1 [일반] [위성] 단추(위성을 고른 모습)
## 단계
| 단계 | 할 일 |
| --- | --- |
| 1 | [위성]을 누르면 위성 사진 위에 지명이 겹쳐 보이는 지도로 바뀜. |
| 2 | 도로 · 하천 이름을 보고 싶으면 [일반]을 누름. |
| 3 | 바꿔도 놓은 점 · 선은 그대로 있음. |
## 보이는 것
- 고른 값은 이 컴퓨터의 화면 취향으로 기억되어, 다음에 열어도 같은 지도로 열림.
- 두 지도 모두 카카오맵이며, 위성은 지명이 함께 나오는 종류임.
- 바탕 지도는 점을 놓는 데 도움을 주는 용도이며 저장되는 값과는 상관 없음.
> 참고: 노선 분석 화면의 바탕은 이 지도가 아니라 받은 위성 사진과 등고선임. [노선 분석](D03/00_설명서) 참고.
@@ -1,31 +0,0 @@
# 단추와 저장
왼쪽 패널 맨 아래 단추 줄 — [?] · [<] · [>] · [저장]과 저장 때 일어나는 일.
## 여는 길
- 기준 지정 화면 왼쪽 패널 맨 아래.
![단추 줄](D02_06_buttons.webp)
그림 번호 — 1 [?] 가이드 · 2 [<] 이전 단계(첫 단계라 흐림) · 3 [>] 다음 단계 · 4 [저장]
## 단계
| 단계 | 할 일 |
| --- | --- |
| 1 | 시점과 종점을 모두 채움. |
| 2 | [저장]을 누름. 두 점이 서버에 저장되고 「지점을 저장했습니다.」 알림이 뜸. |
| 3 | 바로 다음 단계로 가려면 [>]를 누름. 바뀐 것이 있으면 저장한 뒤 [노선 분석](D03/00_설명서) 화면이 열림. |
| 4 | 저장하지 못하면(예: 점이 비어 있음) 이 화면에 머묾. |
## 보이는 것
- 저장하면 표 아래 한 줄이 「저장 좌표계 EPSG:번호」와 시점의 X · Y(m)로 바뀜.
- 다른 단계로 옮길 때(단계 아이콘 · [<] · [>] · 머리 ☰ 메뉴 · 브라우저 뒤로) 고친 것이 있으면 묻지 않고 저장한 뒤 옮겨 감. 저장이 실패하면 옮기지 않고 머묾.
- 산림청이 아닌 계정은 [저장]이 안 보이고, [>]는 저장 없이 그냥 다음 화면으로 감.
- 이미 노선 분석에서 그은 선은 두 점을 옮겨 저장해도 지우지 않음.
> 주의: 두 점의 평균 경도가 126° 나 128° 선을 넘게 옮기면 저장 좌표계가 바뀜. 자세한 내용은 [쓰인 방법과 근거](D02/20_쓰인_방법과_근거) 참고.
> 참고: 연도 범위는 이 화면에서 다루지 않음. 노선 분석에서 구역마다 연도를 고름.
@@ -1,27 +0,0 @@
# 단계 이동 메뉴
☰ 로 진행 단계를 열어 대시보드나 다른 단계 화면으로 옮겨 가는 방법.
## 여는 길
- 화면 왼쪽 위의 ☰ 를 누름.
![진행 단계 메뉴](D02_07_steps_menu.webp)
그림 번호 — 1 진행 단계 목록(대시보드 · 기준 지정 · 노선 분석 · 확정 · 내보내기)
## 단계
| 단계 | 할 일 |
| --- | --- |
| 1 | ☰ 를 누르면 왼쪽에 메뉴가 열림. |
| 2 | 가야 할 단계를 누름. 지금 단계는 강조되어 있음. |
| 3 | 메뉴 밖 어두운 곳을 누르면 닫힘. |
## 보이는 것
- 맨 위 「대시보드」 = 프로젝트 목록 화면으로 돌아감.
- 단계는 기준 지정 → 노선 분석 → 확정 · 내보내기 순.
- 메뉴 아래에는 홈페이지 바로가기 · 언어 · 테마 · 로그아웃이 같이 있음.
> 참고: 기준 지정에서 점을 고친 채 메뉴로 다른 단계에 가도 묻지 않고 저장한 뒤 옮겨 감. 저장이 실패하면 이 화면에 머묾.
-30
View File
@@ -1,30 +0,0 @@
# 칸 뜻
![시점 · 종점 표](D02_01_points_table.webp)
그림 번호는 [시점 · 종점 표](D02/01_시점_종점_표)와 같음.
## 왼쪽 「전체 시점 · 종점」 카드
| 칸 | 뜻 | 규칙 |
| --- | --- | --- |
| 이름 | 「시점」 · 「종점」 | 고정. 줄은 두 개뿐 |
| 📍 | 지도 누름이 놓을 점으로 고름 | 고른 줄이 강조됨 |
| 경도 | 동서 위치(도) | 120 ~ 135 · 소수 가능 |
| 위도 | 남북 위치(도) | 30 ~ 45 · 소수 가능 |
- 경도 · 위도 중 하나만 넣으면 반영되지 않음. 두 칸이 모두 범위 안일 때 반영됨.
- 반영될 때 소수 7 자리(약 1 cm)로 반올림됨.
- 범위 밖 값은 두 칸이 모두 빨갛게 표시되고 반영되지 않음.
## 지도 위
- 표지 하나 = 점 하나. 마우스로 끌어 옮길 수 있음.
- 시점과 종점이 모두 놓이면 둘을 잇는 선 하나가 보임. 노선 분석에서 그을 실제 선이 아니라 두 점 사이를 보여 주는 참고선임.
- 점이 하나도 없으면 우리나라 가운데쯤이 먼저 보임. 점을 놓으면 처음 한 번만 그 점에 맞춰 보이고, 그 뒤로는 사용자가 맞춘 보기(중심 · 확대)를 그대로 둠.
- 지도 위 안내문 = 「지도를 눌러 지점을 놓습니다.」
- 오른쪽 위 [일반] [위성] = 바탕 지도 바꿈. [일반 · 위성 지도](D02/04_일반_위성) 참고.
## 표 아래 한 줄
| 표시 | 뜻 |
| --- | --- |
| 아직 저장 안 함 | 서버에 두 점이 한 번도 저장되지 않음 |
| 저장 좌표계 EPSG:번호 · X · Y | 저장된 좌표계와 시점의 평면좌표(m, 소수 3 자리) |
@@ -1,31 +0,0 @@
# 쓰인 방법과 근거
## 좌표계 처리
- 사용자가 넣는 값은 모두 경도 · 위도(WGS84)임. 지도(카카오맵)도 이 값으로 그려짐.
- 저장할 때 서버가 두 점의 **평균 경도**를 보고 한국 평면좌표(TM) 띠 하나를 고른 뒤, 두 점을 그 좌표계의 X(동쪽) · Y(북쪽) 미터 값으로 바꿔 저장함.
- 노선 분석의 지형 받기 · 선 길이 · 기울기는 이 평면좌표를 씀. 사용자가 변환을 따로 할 필요는 없음.
| 평균 경도 | 좌표계 | 이름 |
| --- | --- | --- |
| 126° 미만 | EPSG:5185 | 서부 |
| 126° 이상 128° 미만 | EPSG:5186 | 중부 |
| 128° 이상 | EPSG:5187 | 동부 |
- 한 프로젝트는 항상 같은 좌표계 한 벌을 씀.
- 동해(5188)는 쓰지 않음.
- 내보내기 때의 좌표계(5179)는 확정 · 내보내기 단계에서 다룸. 이 화면에서는 쓰지 않음.
## 저장 규칙
- 두 점은 프로젝트 한 칸(`plan_base_json`)에 한꺼번에 저장됨. 연도별로 나누어 저장하지 않음.
- 저장은 산림청 계정만 가능함(시스템 관리자 또는 `.go.kr` 메일). 그 밖의 계정이 저장하면 「예정노선 지점은 산림청만 고칠 수 있습니다.」로 막힘.
- 두 점을 옮겨도 이미 그은 선 · 갈래 · 구역은 지우지 않음.
- 두 점이 비어 있는 옛 예정노선(연도별 지점으로 만든 것)은 첫 연도 시점과 마지막 연도 종점을 이어 두 점으로 보여 줌. 저장하면 이 두 점이 정본이 됨.
## 화면 이동 때 저장
- 단계 아이콘 · [<] · [>] · 머리 ☰ 메뉴 · 브라우저 뒤로로 화면을 떠날 때 고친 것이 있으면 저장한 뒤 이동함. 저장이 실패하면 이동하지 않고 머묾.
- 저장하지 않고 고친 값은 같은 브라우저 탭 안에서는 화면을 다시 열어도 남아 있음. 서버에 저장된 값이 그사이 바뀌었으면(노선 분석에서 시점을 고친 경우) 서버 값을 우선함.
## 한계
- 좌표 입력은 경위도만 받음. 평면좌표(X · Y)를 직접 넣는 칸은 없음.
- 지도 위 위치는 카카오맵 화면 기준이며, 정밀한 측량 값이 필요하면 좌표 입력으로 넣을 것.
- 지형 받기와 선 긋기에 관한 설명은 [노선 분석](D03/00_설명서)을 볼 것.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

-11
View File
@@ -1,11 +0,0 @@
[
{"name":"D02_00_overview","route":"#/d02-plan-base","project":"plan","before":[{"wait":".d02-points__row"},{"wait":".ui-kakao-map__box"},{"wait_ms":3500}],"settle_ms":1500,"clip":null,"callouts":[{"n":1,"sel":".d02-points"},{"n":2,"sel":".d02-map__type","pos":"bl"},{"n":3,"sel":".d02-map","pos":"br"},{"n":4,"sel":".ui-sidebar-actions","pos":"tr"}]},
{"name":"D02_01_points_table","route":"#/d02-plan-base","project":"plan","before":[{"wait":".d02-points__row"},{"wait":".ui-kakao-map__box"},{"wait_ms":3500}],"settle_ms":1500,"clip":".d02-panel .ui-sidebar-card","callouts":[{"n":1,"sel":".d02-panel p.d02-info:first-of-type"},{"n":2,"sel":".d02-points__row:nth-child(1)","pos":"tr"},{"n":3,"sel":".d02-points__row:nth-child(1) button","pos":"bl"},{"n":4,"sel":".d02-points__row:nth-child(1) input[data-axis=lon]","pos":"br"},{"n":5,"sel":".d02-points__row:nth-child(1) input[data-axis=lat]","pos":"br"},{"n":6,"sel":".d02-points ~ p.d02-info"}]},
{"name":"D02_02_map","route":"#/d02-plan-base","project":"plan","before":[{"wait":".d02-points__row"},{"wait":".ui-kakao-map__box"},{"wait_ms":3500}],"settle_ms":1500,"clip":".d02-map","callouts":[{"n":1,"sel":".ui-kakao-map__hint"}]},
{"name":"D02_03_pick_point","route":"#/d02-plan-base","project":"plan","before":[{"wait":".d02-points__row"},{"wait":".ui-kakao-map__box"},{"wait_ms":3500},{"click":".d02-points__row:nth-child(2) button"},{"wait":".d02-points__row:nth-child(2).is-active"},{"wait_ms":300}],"settle_ms":1500,"clip":".d02-panel .ui-sidebar-card","callouts":[{"n":1,"sel":".d02-points__row:nth-child(2) button"},{"n":2,"sel":".d02-points__row:nth-child(2)"}]},
{"name":"D02_04_map_toast","route":"#/d02-plan-base","project":"plan","before":[{"wait":".d02-points__row"},{"wait":".ui-kakao-map__box"},{"wait_ms":3500},{"click":".ui-kakao-map__box"},{"wait":"[class*=toast]"},{"wait_ms":400}],"settle_ms":1500,"clip":null,"callouts":[{"n":1,"sel":"[class*=toast]","pos":"tr"}]},
{"name":"D02_05_coordinate_error","route":"#/d02-plan-base","project":"plan","before":[{"wait":".d02-points__row"},{"wait":".ui-kakao-map__box"},{"wait_ms":3500},{"fill":".d02-points__row:nth-child(1) input[data-axis=lon]","value":"200"},{"press":"Tab","sel":".d02-points__row:nth-child(1) input[data-axis=lon]"},{"wait":".d02-points__row:nth-child(1) input.is-error"},{"wait_ms":300}],"settle_ms":1500,"clip":".d02-panel .ui-sidebar-card","callouts":[{"n":1,"sel":".d02-points__row:nth-child(1) input[data-axis=lon]"},{"n":2,"sel":".d02-points__row:nth-child(1) input[data-axis=lat]"}]},
{"name":"D02_06_buttons","route":"#/d02-plan-base","project":"plan","before":[{"wait":".d02-points__row"},{"wait":".ui-kakao-map__box"},{"wait_ms":3500}],"settle_ms":1500,"clip":".ui-sidebar-actions","callouts":[{"n":1,"sel":".ui-sidebar-actions > button:nth-child(1)"},{"n":2,"sel":".ui-sidebar-actions > button:nth-child(2)"},{"n":3,"sel":".ui-sidebar-actions > button:nth-child(3)"},{"n":4,"sel":".ui-sidebar-actions > button:nth-child(4)"}]},
{"name":"D02_07_steps_menu","route":"#/d02-plan-base","project":"plan","before":[{"wait":".d02-points__row"},{"click":".app-burger"},{"wait":".app-drawer__steps .ui-workflow-layout__step"},{"wait_ms":700}],"settle_ms":1500,"clip":null,"callouts":[{"n":1,"sel":".app-drawer__steps","pos":"tr"}]},
{"name":"D02_08_map_type","route":"#/d02-plan-base","project":"plan","before":[{"wait":".d02-points__row"},{"wait":".ui-kakao-map__box"},{"wait_ms":3500},{"click":".d02-map__type button:nth-child(2)"},{"wait_ms":3500}],"settle_ms":1500,"clip":".d02-map__type","callouts":[{"n":1,"sel":".d02-map__type","pos":"bl"}],"clip_pad":240}
]
+2 -2
View File
@@ -4,9 +4,9 @@
## 여는 길
- [기준 지정](D02/00_설명서) 화면에서 [>]를 누르면 두 점을 저장하고 이 화면이 열림.
- [예정노선 등록](D01/00_설명서)에서 [등록]을 누르면 이 화면이 열림(옛 기준 지정 단계는 걷힘).
- 대시보드 프로젝트 표의 단계 막대 「노선 분석」을 누르거나, ☰ 에서 「노선 분석」을 골라도 열림.
- 시점 · 종점이 비어 있으면 「시점 · 종점이 비었습니다 — 1단계(기준 지정)에서 정하세요.」 안내만 나오고 진행이 안 됨.
- 지점은 지도 우클릭 「지점 놓기」로 놓음 · 핀을 끌면 그 자리 노선 점도 같이 옮김 · 지점 없이 긋기 = 우클릭 「여기서 긋기 시작」.
- 긋기 · 지형 받기 · 저장은 산림청만 할 수 있음. 그 밖의 계정은 「보기만 — 산림청 계정에서 긋습니다.」로 보기만 가능함.
![화면 한눈에](D03_00_overview.webp)
+1 -1
View File
@@ -26,7 +26,7 @@
## 보이는 것
- 받을 범위 = 기준 지정의 시점 · 종점을 감싸는 상자에 여유를 더한 것. 여유 처음 값은 350 m 이고 300 ~ 5000 m 까지 쓸 수 있음. 이미 받은 범위가 있으면 그것과 합쳐서 받음.
- 받을 범위 = 처음엔 등록 기준점 둘레 1 km · 그 뒤 지점 · 선 · 지도 보기가 받은 범위 밖으로 가면 조용히 이어 받음(지점 · 선을 감싸는 상자에 여유를 더한 것). 여유 처음 값은 350 m 이고 300 ~ 5000 m 까지 쓸 수 있음. 이미 받은 범위가 있으면 그것과 합쳐서 받음.
- 요약 한 줄 = 「여유 350 m · 도엽 2매(새로 2) · 지형 칸 약 2.1백만」. 도엽은 수치지형도 조각이며 「새로」는 아직 못 받은 조각 수.
- 받기에는 수치지형도(도엽)와 위성 사진이 들어 있음. 위성 사진을 못 받아도 받기가 멈추지 않음. 위성이 없으면 어두운 바탕에 시점 · 종점과 선만 보임.
- 지형 받기는 서버 뒤에서 도는 일이라, 화면을 닫았다 다시 열어도 도는 중이면 이어서 진행률을 보여 줌.
+1 -1
View File
@@ -14,7 +14,7 @@
| 단계 | 할 일 |
| --- | --- |
| 1 | [본선 긋기]를 누름. 단추 글자가 [긋기 끝]으로 바뀌고 선 색이 짙어짐. 처음에는 기준 지정의 시점에서 시작함. |
| 1 | [본선 긋기]를 누름. 단추 글자가 [긋기 끝]으로 바뀌고 선 색이 짙어짐. 지점 · 선 끝 · 선 위에서 시작함(빈 땅은 우클릭 「여기서 긋기 시작」). |
| 2 | 마우스를 움직이면 마지막 점에서 마우스까지 점선이 따라오고, 옆에 길이(m)와 기울기(%)가 보임. |
| 3 | 놓을 자리에서 누름. 그 자리에 점이 찍히고 선이 이어짐. 같은 방법으로 점을 이어 감. |
| 4 | 종점 가까이 가서 누르면 종점에 붙으며 본선이 끝남. 아래 「보이는 것」 참고. |
@@ -8,7 +8,7 @@
![단추 줄](D03_15_buttons.webp)
그림 번호 — 1 [?] 가이드 · 2 [<] 이전 단계(기준 지정) · 3 [>] 다음 단계(확정 · 내보내기) · 4 [저장] · 5 [초기화]
그림 번호 — 1 [?] 가이드 · 2 [<] 이전 단계(첫 단계라 흐림) · 3 [>] 다음 단계(확정 · 내보내기) · 4 [저장] · 5 [초기화]
## 단계
@@ -29,7 +29,7 @@
- 저장하면 구역 연도로 연도별 노선이 만들어지고, 사업 연도 끝은 구역 연도 중 가장 늦은 해가 됨. 서버가 저장된 선으로 다시 셈하므로 화면 값을 그대로 받아 적지 않음.
- 저장에 실패하면 「저장하지 못했습니다 — 까닭」이 뜨고 그은 것은 그대로 남음. 구역에 업체를 이미 지정했다면 그 구역을 없애는 저장은 막힘.
- 단계 아이콘 · [<] · [>] · 머리 ☰ 메뉴 · 브라우저 뒤로로 떠날 때도 바뀐 것이 있으면 저장한 뒤 옮겨 감. 저장이 실패하면 이 화면에 머묾.
- [초기화] 확인 창: 「그은 선 · 갈래 · 구역을 모두 지우고 시점 · 종점만 남깁니다. 저장된 노선도 바뀝니다.」 [초기화]를 누르면 시점 · 종점만 남은 빈 선으로 저장되고 「시점 · 종점만 남겼습니다.」 알림이 뜸. [취소]면 아무것도 바뀌지 않음.
- [초기화] 확인 창: 「그은 선 · 갈래 · 구역을 모두 지웁니다(지점은 남김). 저장된 노선도 바뀝니다.」 [초기화]를 누르면 지점만 남은 빈 노선으로 저장되고 「선을 지웠습니다(지점은 남김).」 알림이 뜸. [취소]면 아무것도 바뀌지 않음.
- 산림청이 아닌 계정은 [저장] · [초기화]가 보이지 않음.
> 주의: [초기화]는 서버에 저장된 노선까지 비움. 이미 업체를 지정한 구역이 있으면 막힐 수 있음.
@@ -4,7 +4,7 @@
## 선을 만드는 방법
- 선은 자동으로 찾지 않고 사용자가 점을 찍어 직선으로 이음. 본선은 기준 지정의 시점에서 시작하고 종점에서 끝나며, 갈래는 기존 선의 점에서 갈라짐.
- 선은 자동으로 찾지 않고 사용자가 점을 찍어 직선으로 이음. 본선은 지점 · 빈 땅에서 시작하고, 갈래는 기존 선의 점에서 갈라짐.
- 구역 마크는 선 위 거리(m)로 기록됨. 마크 사이 구간이 구역(조각)이고, 구역마다 연도를 가짐. 구역 이름은 「선 이름 + 측점 범위」임.
- 측점은 선마다 20 m 간격으로 0+0 부터 셈. 갈래도 0+0 부터 따로 셈.
- 선의 높이는 저장하지 않고, 필요할 때 받은 지형에서 읽음.
+29 -2
View File
@@ -12,8 +12,6 @@ export interface DashboardUser {
role: "SYSTEM_ADMIN" | "ADMIN" | "USER";
is_master: boolean;
status: string;
/** 서버가 하드 삭제 모드인지 (`PROJECT_DELETE_HARD_ENABLED`). GET /me에서만 채워진다. */
project_delete_hard?: boolean;
}
export interface WorkflowStageState {
@@ -464,6 +462,35 @@ export function deleteProject(projectId: string): Promise<unknown> {
return request(`/dashboard/projects/${encodeURIComponent(projectId)}`, { method: "DELETE" });
}
/** 휴지통(592) 한 줄 — days_left = 영구 삭제까지 남은 날 */
export interface TrashProject {
id: string;
name: string;
project_kind?: string | null;
company_id?: number | null;
company_name?: string | null;
deleted_at: string;
deleted_by_name?: string | null;
deleted_by_email?: string | null;
days_left: number;
}
export async function fetchTrashProjects(): Promise<TrashProject[]> {
return (await request<{ projects: TrashProject[] }>("/dashboard/trash/projects")).projects;
}
export function restoreTrashProject(projectId: string): Promise<unknown> {
return request(`/dashboard/trash/projects/${encodeURIComponent(projectId)}/restore`, {
method: "POST",
});
}
export function purgeTrashProject(projectId: string): Promise<unknown> {
return request(`/dashboard/trash/projects/${encodeURIComponent(projectId)}`, {
method: "DELETE",
});
}
export function updateDashboardUser(
userId: number,
payload: AdminUpdateUserRequest,
+73 -5
View File
@@ -116,8 +116,53 @@ async def search_address(query: str, size: int = 10) -> list[dict[str, Any]]:
return await asyncio.to_thread(_search_sync, query.strip(), size)
def _reverse_sync(lon: float, lat: float) -> str | None:
"""좌표 → 주소 한 줄(VWorld getAddress · 도로명 먼저 · 없으면 지번)."""
def _search_place_sync(query: str, size: int) -> list[dict[str, Any]]:
"""장소 이름(POI) 찾기(594-4) — {title, road, parcel, lon, lat} · 실패 = 빈 목록."""
params = urllib.parse.urlencode(
{
"service": "search",
"request": "search",
"version": "2.0",
"crs": "EPSG:4326",
"size": str(size),
"page": "1",
"query": query,
"type": "place",
"format": "json",
"errorformat": "json",
"key": VWORLD_API_KEY,
}
)
try:
body = json.loads(_fetch(f"{_SEARCH_URL}?{params}", timeout=8).decode("utf-8"))
except Exception:
return []
found = []
for item in ((body.get("response") or {}).get("result") or {}).get("items") or []:
address = item.get("address") or {}
point = item.get("point") or {}
try:
lon, lat = float(point["x"]), float(point["y"])
except (KeyError, TypeError, ValueError):
continue
found.append(
{
"title": item.get("title") or "",
"road": address.get("road") or "",
"parcel": address.get("parcel") or "",
"lon": lon,
"lat": lat,
}
)
return found
async def search_place(query: str, size: int = 10) -> list[dict[str, Any]]:
return await asyncio.to_thread(_search_place_sync, query.strip(), size)
def _address_items(lon: float, lat: float) -> dict[str, dict[str, Any]]:
"""좌표 → VWorld getAddress 결과(종류 road · parcel → 한 칸) · 실패 = 빈 dict."""
query = urllib.parse.urlencode(
{
"service": "address",
@@ -133,11 +178,34 @@ def _reverse_sync(lon: float, lat: float) -> str | None:
try:
body = json.loads(_fetch(f"{_GEOCODE_URL}?{query}").decode("utf-8"))
except Exception:
return None
return {}
items = (body.get("response") or {}).get("result") or []
texts = {item.get("type"): item.get("text") for item in items if isinstance(item, dict)}
return texts.get("road") or texts.get("parcel") or None
return {item.get("type"): item for item in items if isinstance(item, dict)}
def _reverse_sync(lon: float, lat: float) -> str | None:
"""좌표 → 주소 한 줄(도로명 먼저 · 없으면 지번)."""
items = _address_items(lon, lat)
return next(
(items[k]["text"] for k in ("road", "parcel") if items.get(k, {}).get("text")), None
)
def _place_sync(lon: float, lat: float) -> dict[str, str | None]:
"""좌표 → {address, region}(591-1 기준점) — 산은 지번 먼저 · region = 「시군구 읍면」."""
items = _address_items(lon, lat)
item = next((items[k] for k in ("parcel", "road") if items.get(k, {}).get("text")), None)
if not item:
return {"address": None, "region": None}
part = item.get("structure") or {}
town = "level4L" if item.get("type") == "parcel" else "level4A" # 도로명은 4L = 길 이름
region = " ".join(v for k in ("level2", "level3", town) if (v := part.get(k)))
return {"address": item["text"], "region": region or None}
async def reverse_geocode(lon: float, lat: float) -> str | None:
return await asyncio.to_thread(_reverse_sync, lon, lat)
async def reverse_place(lon: float, lat: float) -> dict[str, str | None]:
return await asyncio.to_thread(_place_sync, lon, lat)
+3 -3
View File
@@ -137,8 +137,8 @@ async def get_workflow_states_for_projects(
await cursor.execute(
f"""
SELECT project_id, stage_no, stage_key, state, progress_percent, params, message,
DATE_FORMAT(started_at, '%%Y-%%m-%%dT%%H:%%i:%%s') AS started_at,
DATE_FORMAT(completed_at, '%%Y-%%m-%%dT%%H:%%i:%%s') AS completed_at
DATE_FORMAT(started_at, '%%Y-%%m-%%dT%%H:%%i:%%sZ') AS started_at,
DATE_FORMAT(completed_at, '%%Y-%%m-%%dT%%H:%%i:%%sZ') AS completed_at
FROM project_workflow_stages
WHERE project_id IN ({format_strings})
ORDER BY project_id, stage_no ASC
@@ -505,7 +505,7 @@ async def get_system_resources(days: int = 30) -> dict[str, Any]:
bucket_seconds = 120
else:
bucket_seconds = max(120, (days * 86400) // target_points)
date_fmt = "%%Y-%%m-%%dT%%H:%%i:%%s"
date_fmt = "%%Y-%%m-%%dT%%H:%%i:%%sZ"
await cursor.execute(
f"""SELECT
DATE_FORMAT(
@@ -0,0 +1,74 @@
"""B01_Dashboard 프로젝트 휴지통(592) — 목록 · 되살리기 · 영구 삭제는 common_util_project_delete."""
from typing import Any
import aiomysql
from common_util.common_util_audit import record_audit
from common_util.common_util_project_delete import DELETED_BY_SQL
from config.config_db import get_db_pool
from config.config_system import TRASH_RETENTION_DAYS
def days_left(seconds_left: int | None) -> int:
"""영구 삭제까지 남은 날 — 하루 덜 찼어도 하루로(올림) · 지났으면 0."""
if seconds_left is None or seconds_left <= 0:
return 0
return -(-int(seconds_left) // 86400)
async def list_trash_projects(company_id: int | None = None) -> list[dict[str, Any]]:
"""휴지통 — 최근 지운 것부터 · company_id 면 그 회사 것만."""
where = " AND p.company_id = %s" if company_id is not None else ""
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor:
await cursor.execute(
f"""SELECT t.*, du.name AS deleted_by_name, du.email AS deleted_by_email
FROM (SELECT p.id, p.name, p.project_kind, p.company_id, c.name AS company_name,
p.deleted_at, {DELETED_BY_SQL} AS deleted_by,
TIMESTAMPDIFF(SECOND, CURRENT_TIMESTAMP,
p.deleted_at + INTERVAL %s DAY) AS seconds_left
FROM projects p LEFT JOIN companies c ON c.id = p.company_id
WHERE p.deleted_at IS NOT NULL{where}) t
LEFT JOIN users du ON du.id = t.deleted_by
ORDER BY t.deleted_at DESC""",
(TRASH_RETENTION_DAYS, company_id)
if company_id is not None
else (TRASH_RETENTION_DAYS,),
)
rows = list(await cursor.fetchall())
for row in rows:
row["days_left"] = days_left(row.pop("seconds_left"))
return rows
async def get_trash_project(project_id: str) -> dict[str, Any] | None:
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor:
await cursor.execute(
"SELECT id, company_id, name FROM projects WHERE id = %s AND deleted_at IS NOT NULL",
(project_id,),
)
return await cursor.fetchone()
async def restore_project(project_id: str, actor_id: int, request: Any | None = None) -> bool:
pool = get_db_pool()
async with pool.acquire() as connection, connection.cursor() as cursor:
await connection.begin()
await cursor.execute(
"UPDATE projects SET deleted_at = NULL WHERE id = %s AND deleted_at IS NOT NULL",
(project_id,),
)
changed = cursor.rowcount > 0
if changed:
await record_audit(
cursor,
actor_id=actor_id,
action="PROJECT_RESTORE",
resource_type="project",
resource_ref=project_id,
request=request,
)
await connection.commit()
return changed
+5 -7
View File
@@ -11,9 +11,8 @@ from common_util import common_util_calc_sites as calc_sites
from common_util.common_util_audit import record_audit
from common_util.common_util_auth import require_company, require_system_admin, verify_session
from common_util.common_util_email import send_email
from common_util.common_util_project_delete import hard_delete_project
from config.config_db import get_db_pool
from config.config_system import APP_PUBLIC_BASE_URL, PROJECT_DELETE_HARD_ENABLED
from config.config_system import APP_PUBLIC_BASE_URL
from .B01_Dashboard_Knowledge_Chat import router as knowledge_chat_router
from .B01_Dashboard_Map import fetch_base_tile, geocode_address, search_address
@@ -58,6 +57,7 @@ from .B01_Dashboard_Repository_Members import (
set_project_members,
)
from .B01_Dashboard_Router_Assets import router as assets_router
from .B01_Dashboard_Router_Trash import router as trash_router
from .B01_Dashboard_Schema import (
AddMemberRequest,
AdminUpdateUserRequest,
@@ -106,8 +106,6 @@ async def dashboard_me(session: dict[str, Any] = Depends(verify_session)):
user = await get_dashboard_me(int(session["user_id"]))
if not user:
raise HTTPException(status_code=404, detail="사용자 정보를 찾을 수 없습니다.")
# 삭제 버튼이 원본까지 지우는 모드인지 화면에 알려 경고 문구를 바꾸게 한다.
user["project_delete_hard"] = PROJECT_DELETE_HARD_ENABLED
return {"status": "success", "user": user}
@@ -418,9 +416,8 @@ async def dashboard_delete_project(
if not can_del:
raise HTTPException(status_code=403, detail="프로젝트 삭제 권한이 없습니다.")
# 개발 PC에서만 하드 삭제. 배포 기본값은 지금까지처럼 소프트 삭제다.
delete_project = hard_delete_project if PROJECT_DELETE_HARD_ENABLED else soft_delete_project
if not await delete_project(project_id, int(session["user_id"]), request):
# 지움 = 늘 휴지통(592) · 영구 삭제는 휴지통 [영구 삭제] · 기한 지나면 정리 루프.
if not await soft_delete_project(project_id, int(session["user_id"]), request):
raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다.")
return {"status": "success"}
@@ -616,4 +613,5 @@ async def write_calc_sites(
router.include_router(assets_router)
router.include_router(trash_router)
router.include_router(knowledge_chat_router)
+31 -16
View File
@@ -1,12 +1,13 @@
"""B01 대시보드 홈 API (PLAN 547) — 프로젝트 카드 요약 · 역할별 홈."""
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Any
import aiomysql
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, HTTPException, Query
from common_util.common_util_auth import is_forest_email, verify_session
from common_util.common_util_time import utc_now_naive, utc_text
from config.config_db import get_db_pool
from .B01_Dashboard_Repository import get_system_resources
@@ -111,13 +112,13 @@ async def _todos(cursor: Any, session: dict[str, Any], role: str) -> dict[str, A
(int(session["user_id"]),),
)
batches = list(await cursor.fetchall())
soon = datetime.now() + timedelta(days=EXPIRE_SOON_DAYS)
soon = utc_now_naive() + timedelta(days=EXPIRE_SOON_DAYS)
todos["expiring_temp"] = [
{
"batch_id": str(b["id"]),
"name": b["name"],
"expires_at": b["expires_at"].isoformat(),
"days_left": max(0, (b["expires_at"] - datetime.now()).days),
"expires_at": utc_text(b["expires_at"]),
"days_left": max(0, (b["expires_at"] - utc_now_naive()).days),
}
for b in batches
if b["expires_at"] and b["expires_at"] <= soon
@@ -138,22 +139,36 @@ async def _company(cursor: Any, company_id: int) -> dict[str, Any]:
async def _forest(cursor: Any, company_id: int) -> dict[str, Any]:
"""예정노선 연도 상태별 수(분석 전 · 분석됨 · 확정) · 업체 지정 대기(확정 · 업체 없음)."""
"""예정노선 수 — 분석 전 = 노선을 아직 안 그은 프로젝트 · 분석됨 · 확정 = 연도(설계 내용 파일) ·
업체 지정 대기 = 확정 연도 중 그 연도 세트에 업체가 하나도 없는 것(업체 줄 = 세트 열쇠 ·
옛 조각 id = 든 세트)."""
from D03_PlanAnalysis import D03_PlanAnalysis_Store as store
await cursor.execute(
"""SELECT y.route_json IS NOT NULL AS analyzed, y.confirmed_at IS NOT NULL AS confirmed,
EXISTS (SELECT 1 FROM plan_route_assignees a
WHERE a.project_id = y.project_id AND a.year = y.year) AS assigned
FROM plan_route_years y
JOIN projects p ON p.id = y.project_id AND p.deleted_at IS NULL
WHERE p.company_id = %s""",
"""SELECT p.id, a.piece_id FROM projects p
LEFT JOIN plan_route_assignees a ON a.project_id = p.id
WHERE p.company_id = %s AND p.project_kind = 'PLAN' AND p.deleted_at IS NULL""",
(company_id,),
)
assigned: dict[str, set] = {}
for r in await cursor.fetchall():
assigned.setdefault(str(r["id"]), set()).add(r["piece_id"])
status = {"draft": 0, "analyzed": 0, "confirmed": 0}
waiting = 0
for r in await cursor.fetchall():
key = "confirmed" if r["confirmed"] else "analyzed" if r["analyzed"] else "draft"
status[key] += 1
waiting += bool(r["confirmed"] and not r["assigned"])
for project_id, pieces in assigned.items():
try:
state = await store.plan_state(project_id)
except HTTPException: # 노선 파일이 틀린 프로젝트 = 건너뜀
continue
if not state["plan"]["lines"]:
status["draft"] += 1
found = (store.find_unit(state["units"], key) for key in pieces)
held = {unit["id"] for unit in found if unit}
for year, row in state["years"].items():
key = "confirmed" if row["confirmed_at"] else "analyzed"
status[key] += 1
ids = {u["id"] for u in state["units"] if u["year"] == year}
waiting += bool(row["confirmed_at"] and not ids & held)
return {"plan_status": status, "assign_waiting": waiting}
+21 -8
View File
@@ -14,6 +14,7 @@ import aiomysql
from fastapi import APIRouter, Depends, HTTPException, Query
from common_util.common_util_auth import verify_session
from common_util.common_util_time import utc_now_naive
from config.config_db import get_db_pool
from .B01_Dashboard_Repository import _project_rows
@@ -22,7 +23,7 @@ from .B01_Dashboard_Summary import project_summaries
router = APIRouter(prefix="/api/dashboard/overview", tags=["B01_Dashboard"])
#: 종류별 단계 수 — 화면 단계 막대(`group_workflow.ts` · `WORKFLOW_STEP_ROUTES`)와 같음
STAGE_TOTAL = {"ROAD": 7, "PLAN": 3, "STABILITY": 6}
STAGE_TOTAL = {"ROAD": 7, "PLAN": 2, "STABILITY": 6}
#: 손대지 않은 날이 이만큼 넘으면 「오래 멈춤」
STALE_DAYS = 14
@@ -64,7 +65,7 @@ def idle_days(updated_at: datetime | None, now: datetime | None = None) -> int |
"""마지막 수정 뒤 지난 날 수."""
if updated_at is None:
return None
return ((now or datetime.now()) - updated_at).days
return ((now or utc_now_naive()) - updated_at).days
def is_mine(row: dict[str, Any], user_id: int) -> bool:
@@ -232,6 +233,17 @@ def _link(kind: str, row: dict[str, Any], note: str | None = None) -> dict[str,
}
async def _plan_year_note(project_id: str, piece_id: str | None) -> str | None:
"""「N 년」 — 그 조각의 연도(설계 내용 파일) · 조각 · 파일이 없으면 None."""
from D04_PlanConfirm.D04_PlanConfirm_Link import piece_route
try:
year = (await piece_route(project_id, piece_id))["year"]
except HTTPException:
return None
return f"{year} 년" if year else None
async def _links(cursor: Any, row: dict[str, Any]) -> list[dict[str, Any]]:
"""연결된 것 — 원본 · 이 프로젝트에서 나온 것 · 연결한 예정노선(원본과 같으면 한 번만)."""
links: list[dict[str, Any]] = []
@@ -249,16 +261,17 @@ async def _links(cursor: Any, row: dict[str, Any]) -> list[dict[str, Any]]:
)
links += [_link("derived", r) for r in await cursor.fetchall()]
await cursor.execute(
"""SELECT p.id, p.name, a.year FROM plan_route_assignees a
"""SELECT p.id, p.name, a.piece_id FROM plan_route_assignees a
JOIN projects p ON p.id = a.project_id AND p.deleted_at IS NULL
WHERE a.linked_project_id = %s ORDER BY a.year""",
WHERE a.linked_project_id = %s ORDER BY a.id""",
(row["id"],),
)
for r in await cursor.fetchall():
note = await _plan_year_note(str(r["id"]), r["piece_id"])
if r["id"] == source:
links[0]["note"] = f"{r['year']} 년"
links[0]["note"] = note
else:
links.append(_link("plan", r, f"{r['year']} 년"))
links.append(_link("plan", r, note))
return links
@@ -269,7 +282,7 @@ _AUDIT_ACTIONS = ("PROJECT_CREATE", "PROJECT_UPDATE")
async def _changes(cursor: Any, project_id: str, stages: list[dict[str, Any]]) -> list[dict]:
"""최근 변경 — 감사 기록(만듦 · 정보 고침) + 단계 마친 때 · 새것부터 8 개."""
await cursor.execute(
"""SELECT l.action, DATE_FORMAT(l.timestamp, '%%Y-%%m-%%d %%H:%%i:%%s') AS at,
"""SELECT l.action, DATE_FORMAT(l.timestamp, '%%Y-%%m-%%dT%%H:%%i:%%sZ') AS at,
u.name AS who
FROM system_audit_logs l LEFT JOIN users u ON u.id = l.user_id
WHERE l.resource_type = 'project' AND l.resource_ref = %s
@@ -280,7 +293,7 @@ async def _changes(cursor: Any, project_id: str, stages: list[dict[str, Any]]) -
items: list[dict[str, Any]] = [dict(r) for r in await cursor.fetchall()]
items += [
{
"at": s["completed_at"].replace("T", " "),
"at": s["completed_at"],
"who": None,
"action": "STAGE_COMPLETE",
"stage_no": s["stage_no"],
@@ -0,0 +1,51 @@
"""B01_Dashboard 프로젝트 휴지통 API(592) — 본체 라우터가 include 함.
보기 · 되살리기 = 시스템 관리자(전부) · 회사 관리자(자기 회사) · 영구 삭제 = 시스템 관리자만.
"""
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Request
from common_util.common_util_auth import require_system_admin
from common_util.common_util_project_delete import hard_delete_project
from .B01_Dashboard_Repository_Trash import get_trash_project, list_trash_projects, restore_project
from .B01_Dashboard_Scope import _require_company_id, _same_company, require_company_admin
router = APIRouter()
_GONE = "휴지통에 없는 프로젝트입니다."
@router.get("/trash/projects")
async def trash_projects(session: dict[str, Any] = Depends(require_company_admin)):
company_id = None if session["role"] == "SYSTEM_ADMIN" else _require_company_id(session)
return {"status": "success", "projects": await list_trash_projects(company_id)}
@router.post("/trash/projects/{project_id}/restore")
async def trash_restore(
project_id: str,
request: Request,
session: dict[str, Any] = Depends(require_company_admin),
):
project = await get_trash_project(project_id)
if not project:
raise HTTPException(status_code=404, detail=_GONE)
if session["role"] != "SYSTEM_ADMIN" and not _same_company(session, project.get("company_id")):
raise HTTPException(status_code=403, detail="다른 회사의 프로젝트는 되살릴 수 없습니다.")
if not await restore_project(project_id, int(session["user_id"]), request):
raise HTTPException(status_code=404, detail=_GONE)
return {"status": "success"}
@router.delete("/trash/projects/{project_id}")
async def trash_purge(
project_id: str,
request: Request,
session: dict[str, Any] = Depends(require_system_admin),
):
if not await hard_delete_project(project_id, int(session["user_id"]), request):
raise HTTPException(status_code=404, detail=_GONE)
return {"status": "success"}
+4 -4
View File
@@ -9,12 +9,12 @@
from __future__ import annotations
import json
from datetime import datetime
from pathlib import Path
from typing import Any
import aiomysql
from common_util.common_util_time import utc_now_naive, utc_text
from config.config_db import get_db_pool
from M02_MasterTemplete.M02_Template_Migrate import project_root
@@ -25,7 +25,7 @@ THUMB_POINTS = 100
# 단계 이름 = 화면 단계 막대 글(ui_template_locale_common · _de) 한국어 · 모르는 종류 = 임도
STAGE_NAMES: dict[str, tuple[str, ...]] = {
"ROAD": ("파일입력", "전처리", "노선설계", "횡단설계", "상세설계", "수량산출", "원가계산"),
"PLAN": ("기준 지정", "노선 분석", "확정 · 내보내기"),
"PLAN": ("예정노선", "확정 · 내보내기"),
"STABILITY": ("지반 조건", "2D 해석", "3D 해석", "암반 해석", "광역 위험", "보고서"),
}
@@ -125,7 +125,7 @@ async def project_summaries(project_ids: list[str]) -> list[dict[str, Any]]:
)
stations = {int(r["route_id"]): int(r["n"]) for r in await cursor.fetchall()}
now = datetime.now()
now = utc_now_naive()
result = []
for pid in ids:
row = rows.get(pid)
@@ -159,7 +159,7 @@ async def project_summaries(project_ids: list[str]) -> list[dict[str, Any]]:
"stage_total": len(names),
"stage_name": names[index],
"done": bool(stages) and all(s["state"] == "COMPLETE" for s in stages),
"updated_at": updated.isoformat() if updated else None,
"updated_at": utc_text(updated),
"stale_days": (now - updated).days if updated else None,
"owner_user_id": row.get("user_id"),
"owner_names": owners,
+4 -10
View File
@@ -7,6 +7,7 @@ import {
showToast,
} from "@ui/ui_template_elements";
import type { DashboardUser } from "./B01_Dashboard_Api_Fetch";
import { dateText, timeText } from "../common_util/common_util_time";
/**
* 목록형 컨테이너가 한 번에 보여 주는 행 수. 넘치는 만큼은 컨테이너 안에서 스크롤한다 —
@@ -36,16 +37,9 @@ export async function runRequest(action: () => Promise<unknown>): Promise<void>
}
}
export function formatDate(value?: string | null): string {
return value ? value.slice(0, 10) : "-";
}
/** 시:분 — 표에서 날짜 아래 줄에 붙인다 (2026-09-06 사용자 지시). */
export function formatTime(value?: string | null): string {
if (!value) return "";
const time = value.includes("T") ? value.split("T")[1] : value.slice(11);
return time ? time.slice(0, 5) : "";
}
/** 날짜 · 시:분 — 서버 UTC 를 보는 PC 시간대로(`common_util_time`). 시:분은 표에서 날짜 아래 줄에 붙인다. */
export const formatDate = (value?: string | null): string => dateText(value);
export const formatTime = (value?: string | null): string => timeText(value);
/**
* 위·아래 두 줄짜리 표 칸. 아랫줄은 작은 글씨라 **행 높이는 한 줄일 때와 같다**.
+3 -2
View File
@@ -9,6 +9,7 @@ import { createButton, el } from "@ui/ui_template_elements";
import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflow_nav";
import { groupFlowOf } from "../A00_Common/group_workflow";
import type { ProjectSummary } from "./B01_Dashboard_Api_Home";
import { calendarDaysAgo } from "../common_util/common_util_time";
import { L } from "./B01_Dashboard_UI_Common";
import { isStale } from "./logic/B01_Dashboard_Home_Layout";
@@ -59,10 +60,10 @@ export const stageText = (card: ProjectSummary): string =>
/** 「n 일째 그대로」 · 오늘 · n 일 전 */
export function whenText(card: ProjectSummary): string {
const days = card.stale_days ?? null;
const days = calendarDaysAgo(card.updated_at) ?? card.stale_days ?? null;
if (days === null) return "";
if (isStale(card)) return `${days} 일째 그대로`;
return days === 0 ? "오늘" : `${days} 일 전`;
return days <= 0 ? "오늘" : `${days} 일 전`;
}
export function continueStage(card: ProjectSummary): void {
+2 -5
View File
@@ -303,13 +303,10 @@ export async function openEditProjectModal(
);
}
export function openDeleteProjectModal(user: DashboardUser, project: ProjectItem): void {
export function openDeleteProjectModal(_user: DashboardUser, project: ProjectItem): void {
const warning = document.createElement("p");
warning.className = "b01-dashboard__modal-text";
// 하드 삭제 모드에서는 업로드 원본까지 사라진다 — 문구를 바꿔 실수로 날리는 걸 막는다.
warning.textContent = user.project_delete_hard
? L("B01_Dashboard_Confirm_DeleteProject_Hard")
: L("B01_Dashboard_Confirm_DeleteProject");
warning.textContent = L("B01_Dashboard_Confirm_DeleteProject");
openModal(L("B01_Dashboard_DeleteProject"), [warning], async () => {
await deleteProject(project.id);
@@ -28,6 +28,7 @@ import {
stageProgress,
} from "./B01_Dashboard_UI_ProjectList_Parts";
import { openMenuAt, rowMenuItems } from "./B01_Dashboard_UI_ProjectList_Views";
import { dateText, dateTimeText } from "../common_util/common_util_time";
/** 인쇄 · 내려받기 화면(B09 초안 길) — 도면 PDF · 수량 · 내역 xlsx 를 받는 곳 */
const PRINT_ROUTE = "b09-print" as RoutePath;
@@ -117,7 +118,7 @@ function infoPane(detail: ProjectDetail): HTMLElement {
["시행청", p.client_org],
["사업 번호", p.project_number],
["담당", people],
["만든 날", (p.created_at ?? "").slice(0, 10)],
["만든 날", dateText(p.created_at, "")],
];
const kv = el("div", { className: "b01-pd-kv" });
for (const [label, value] of rows) {
@@ -178,7 +179,7 @@ function changeText(change: ProjectChange, detail: ProjectDetail): string {
: change.action === "PROJECT_CREATE"
? "만듦"
: "정보 고침";
return [change.at.slice(0, 16), change.who, what].filter(Boolean).join(" · ");
return [dateTimeText(change.at, ""), change.who, what].filter(Boolean).join(" · ");
}
function changePane(detail: ProjectDetail): HTMLElement | null {
@@ -9,6 +9,7 @@ import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflo
import { groupFlowOf } from "../A00_Common/group_workflow";
import type { RoutePath } from "@config/config_frontend";
import type { BoardColumn, OverviewProject } from "./B01_Dashboard_Api_Projects";
import { agoText } from "../common_util/common_util_time";
import { L } from "./B01_Dashboard_UI_Common";
/** 종류 — 칩 · 배지 차례 · 사방설계 = 준비 중 */
@@ -121,10 +122,5 @@ export function thumbSvg(points: number[][] | null | undefined): SVGElement {
/** 「오늘」 · 「어제」 · 「n 일 전」 · 그 밖 날짜 */
export function ago(project: OverviewProject): string {
const days = project.idle_days;
if (days === null || days === undefined) return "-";
if (days <= 0) return "오늘";
if (days === 1) return "어제";
if (days < 7) return `${days} 일 전`;
return (project.updated_at ?? "").slice(0, 10);
return agoText(project.updated_at);
}
@@ -11,6 +11,7 @@ import {
import { el } from "@ui/ui_template_elements_base";
import type { DashboardUser } from "./B01_Dashboard_Api_Fetch";
import type { OverviewProject } from "./B01_Dashboard_Api_Projects";
import { dateText } from "../common_util/common_util_time";
import { canDeleteProject, canEditProject } from "./B01_Dashboard_UI_Helper";
import { openDeleteProjectModal, openEditProjectModal } from "./B01_Dashboard_UI_Modals";
import {
@@ -148,7 +149,7 @@ export function projectRows(
}),
el("td", { text: boardLabel(project.board) }),
el("td", { text: project.manager_name ?? "-" }),
el("td", { text: (project.updated_at ?? "").slice(0, 10) || "-" }),
el("td", { text: dateText(project.updated_at) }),
el("td", { children: [more] }),
],
});
+2 -1
View File
@@ -12,6 +12,7 @@ import { icon, type IconName } from "@ui/ui_template_icons";
import { currentLanguageIndex, setLanguage } from "@ui/ui_template_locale";
import { readTouchMode, setTouchMode, type TouchMode } from "../A00_Common/device_context";
import { currentQuery, navigateTo, type MountedPage } from "../A00_Common/router";
import { parseUtcMs } from "../common_util/common_util_time";
import {
fetchAllCompanies,
fetchCompanyJoinRequests,
@@ -216,7 +217,7 @@ async function refreshBadges(ctx: ViewCtx, nav: DashboardNav, bell: HTMLElement)
const newPlans = plans.filter((p) => !p.linked_project_id || p.update_available).length;
const now = Date.now();
const expiring = temp.filter(
(b) => b.expires_at && Date.parse(b.expires_at) - now < EXPIRE_SOON_MS,
(b) => b.expires_at && (parseUtcMs(b.expires_at) ?? Infinity) - now < EXPIRE_SOON_MS,
).length;
nav.setBadge("people", joins.length, true);
nav.setBadge("assigned", newPlans, true);
+2 -7
View File
@@ -29,6 +29,7 @@ import {
uploadTempChunk,
type TempBatchItem,
} from "./B01_Dashboard_Api_Temp";
import { dateText } from "../common_util/common_util_time";
import { L } from "./B01_Dashboard_UI_Common";
import {
badge,
@@ -73,12 +74,6 @@ function taskKey(file: File): string {
return `${file.name}::${file.size}`;
}
function formatDate(value: string | null): string {
if (!value) return "-";
const date = new Date(value);
return Number.isNaN(date.getTime()) ? "-" : date.toLocaleDateString();
}
const COLUMNS: Column[] = [
{ label: "보관 이름" },
{ label: "상태", width: "120px" },
@@ -263,7 +258,7 @@ export function buildTempBox(onGuide: () => void): TempBox {
nameWithCaret(batch, opened),
status,
leftCell(batch),
formatDate(batch.created_at),
dateText(batch.created_at),
rowMenu([
{ label: L("B01_Temp_Btn_Add"), onClick: () => openAddModal(batch) },
{

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