feat(478-2 문서 사이트): 묶음 규칙 · 원고 생성 · mkdocs 설정 · 손글 옮김 · 검사 시험(가짜 추출)

- docs/tools/groups.yml · group.py — 함수를 로직 묶음에 자동 배정(계산 등록부 id · 파일 이름 무늬 · 안 맞음 = 묶음 없음 · 함수 목록 지문)
- docs/tools/build.py — 추출 JSON + 손글로 페이지 · 묶음 화면 · 함수 칸 · mermaid 흐름 · 세션 칸 · 서버 주소 · 설명 확인 필요 표시 · 함수 화면은 TypeDoc(markdown) · mkdocstrings(griffe)
- docs/mkdocs.yml · nav_hook.py — Material · 한국어 · 검색 · mermaid · 왼쪽 메뉴 페이지 → 묶음 → 함수
- docs/guide/B05_B06 — 8 파일을 page.md(개요 · 규칙 · 사용자 결정) + groups/*.md 17 개로 줄여 옮김
- resources/tester/test_478_docs.py · fixtures/478_docs — 가짜 추출로 배정 · 사이트 strict · 도구 대 추출 · 진짜 추출은 있을 때만

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 18:53:43 +09:00
co-authored by Claude Opus 5.5
parent bbc0f53f50
commit 2e6086c5ee
40 changed files with 1865 additions and 415 deletions
-71
View File
@@ -1,71 +0,0 @@
---
type: concept
status: stable
related_pages: ["[[design_page/index]]", "[[design_page/values]]", "[[design_page/rules]]"]
last_updated: 2026-10-19
source: ["docs/raw/조사/2026-10-19_472_구조다시세움_목록.md", "PLAN.md 467"]
---
# 계산 목록 — TS · 파이썬 짝 · Node 묶음 · 실행 자리
- Node 묶음 — sc = server-calc · co = corridor · ex = b07-extent · dc = design-calc(등록부 입구 `A00_Common/design/logic/design_calc_node.ts` · 파이썬 `common_util_calc_runner.run_calc`) · 다시 지음 `npm run -s build:server-calc|corridor|b07-extent|design-calc`.
- 실행 자리 — 짝 = 브라우저 즉시 TS + [저장] 정본 파이썬 · ② = 같은 TS 를 브라우저 · Node 둘 다 · 브라우저 = 입력 도구 · 표시만 · 서버 = 무거운 GIS · 추천.
- 줄 더함 · 바꿈 = 새 계산 · 짝 · 묶음이 바뀐 커밋에서.
| # | 계산 | TS | 파이썬 짝 | Node | 실행 자리 · 거울 시험 |
|---|---|---|---|---|---|
| R1 | 평면 폴리라인 · 곡선(BC · EC · MC) · 선 자리 x · y 4자리 한 벌(초안 그린 선 = 계획노선 csv = 노선 geojson) | `logic/B05_Profile_UI_RouteEdit_Curve.ts:243` · `logic/B05_Profile_Pipeline_RouteGround.ts:66` draftLine | `common_util_route_polyline.py:579` · `common_util_route_geometry.py` write_route_csv · `B05_Profile_Engine.py:24` _route_geojson | — | 짝 · `test_route_polyline_browser_mirror` · `test_475_1_route_digits` |
| R2 | 손잡이 → IP · R | `logic/B05_Profile_UI_RouteEdit_Curve.ts` dragHandleTo | — | — | 브라우저 · `test_route_polyline_handle_drag` |
| R3 | 노드 편집 규칙(곡선 한도 · 고정점) | `logic/B05_Profile_UI_RouteEdit_Edits.ts` | `B05_Profile_Route_NodeEdits.py:21` | — | 짝 · `test_430_route_edit_rules` |
| R4 | 스냅 맞춤(MC → 일반측점) | `logic/B05_Profile_RouteEdit_StationFit.ts` | — | — | 브라우저 · `test_366_3_station_fit` |
| R5 | 측점 틀(법선 · 방위) | `logic/B05_Profile_UI_RouteEdit_Frame.ts:61` | `B05_Profile_Engine_Sections_Core.py:449-519` | — | 짝(입력 선 다름) · `test_b05_routeedit_frame_mirror` |
| R6 | 측점 목록 | `logic/B05_Profile_Sections_Stations.ts:276` · `logic/design_pipeline_stations.ts` | `B05_Profile_Engine_Sections_Core.py:449` | — | 짝 · `test_344_3_pipeline_stations_mirror` · `test_407_curve_station` |
| R7 | 측점 열쇠 | `common_util_station_key.ts:32` | `common_util_station_key.py:32` | — | 짝 · `test_344_3_station_key_mirror` |
| R8 | 측점 갈아끼움 | `logic/B06_Section_Station_Swap.ts:189` | — | — | 브라우저 · `test_346_2_station_swap` · `test_272_swap_keeps_canon` |
| G1 | 지반 표본 | `common_util_surface_sampler.ts:65` · `B05_Profile_Api_TerrainGrid.ts:33-99` | `common_util_surface_sampler.py:210`(npz) | ex | 짝 · `test_344_1_surface_sampler_mirror` |
| G2 | 미교차 넓힘 표본 | `logic/B05_Profile_Sections_Ground.ts:269` | `B06_Section_Engine_SampleExtend.py:62` | — | 짝 · `test_344_1_sections_ground_mirror` |
| G3 | 넓힘(표본 폭 넘음) | — | `B06_Section_Router_Widen.py:80` | — | 서버(저장 밖 DB 씀) |
| G4 | 상단측 | `logic/B05_Profile_Pipeline_Detail.ts` uphillSide | `B05_Profile_Engine_Sections_Core.py` uphill_side | — | 짝 · `test_346_2_pipeline_cross_page` |
| P1 | 첫 계획선(base PVI) | `logic/B05_Profile_Grade_Ground.ts:114` · `logic/B05_Profile_Pipeline_Grade.ts:285` | `B05_Profile_Engine_Grade_Profile.py:537` | — | 짝 · `test_344_2_route_pairs` |
| P2 | 계획선 짓기 | `logic/B05_Profile_UI_Profile_Alignment.ts:401` | `B05_Profile_Engine_Grade_Alignment.py:494` | — | 짝 · `test_426_profile_straight` · `test_213_1_hidden_station_pvi` · `test_438_1_initial_straighten` |
| P3 | 종단곡선 법칙(L · R = L/Δ) | `logic/B05_Profile_Profile_CurveLaw.ts` | `B05_Profile_Engine_Grade_CurveLaw.py` | — | 짝 · `test_432_1_curve_law` |
| P4 | 직선 판정 | `logic/B05_Profile_Profile_Straight.ts:21` | `B05_Profile_Engine_Grade_Alignment.py` | — | 짝 · `test_426_profile_straight` · `test_271_straighten_pick` |
| P5 | 노선 바뀜 계획고 옮김 | `logic/B05_Profile_Grade_Carry.ts:188` | `B05_Profile_Engine_Grade_Carry.py:149` | — | 짝 · `test_400_profile_carry` · `test_349_0v_route_save_span` |
| P6 | 옮긴 시설 꼭짓점 세움 | `logic/B05_Profile_Grade_Seat.ts:32` · `common_util_structure_station.ts:356` | `B05_Profile_Router_Confirm.py:220 · 306` | — | 짝 · `test_427_1_follow_seat` · `test_459_route_seat_mirror` |
| P7 | 직선화 · 자유지정 · 쉬프트 · 시종점 | `logic/B05_Profile_UI_Profile_Straighten.ts` · `logic/design_profile_free.ts` · `logic/B05_Profile_UI_Profile_Pivot.ts` · `logic/B05_Profile_EndOffsets.ts` | — | — | 브라우저(편집값만 저장 → P2) |
| P8 | 종단 경고(기울기 · 법정 L · 최소 R · 피복) | `B05_Profile_UI_Panel_Fields.ts` · `logic/B05_Profile_UI_Profile_Guard.ts` · `B05_Profile_UI_Profile_MinCover.ts` · 경고 글 `logic/B05_Profile_UI_Profile_Alignment.ts` buildAlignment | `B05_Profile_Engine_Grade.py` · `B05_Profile_PageSettings.py` · 경고 글 `B05_Profile_Engine_Grade_Alignment.py` build_alignment | — | 짝 · [저장] · [확정] = `B06_Section_Server_Calc_Prebuild.py` refresh_alignment 가 경고만 다시 씀(기하 그대로) · `test_442_2_grade_limit_mirror` · `test_472_save_warnings` |
| C1 | 횡단 설계선 | `common_util_cross_design.ts:267` · `logic/B06_Section_Cross_Stage.ts:93` · `B06_Section_Cross_Refresh.ts:330` | `B06_Section_Engine_Design.py:195`(B07 · 추천 · Route_Save · B05 확정 · 넓힘 찾기) | dc · ex | ② · [저장] · [확정] 다시 셈 = Node(`B06_Section_Engine_Design_Node.py` 측점 묶음 · 파이썬은 입력 조립) · `test_b06_cross_design_mirror` · `test_472_1_golden` 설계선 점 1e-4 |
| C2 | 단면적(절 · 성 · 측구 · 층따기) | `common_util_cross_design_areas.ts` | `B06_Section_Engine_Areas.py` | dc · sc · ex | ② · C1 과 한 셈 · 정본 = 표준 · 구조물 측점 모두 Node |
| C3 | 확정 보정(포장 · 세월교 · 기울임) | `logic/B06_Section_Cross_Enforce.ts:40` | `B06_Section_Router_Design_Ranges.py` · `B06_Section_Engine_Tilt.py` | dc(다시 세운 설계) | 구간 판정 짝 · `test_349_0n_cross_enforce_mirror` |
| C4 | 소단 구간(구조물 → 구간) | `common_util_berm_spans.ts`(시설 줄 · 다시 셈이 부름) | `B06_Section_Engine_Structures_Wall.py:124`(Node 못 부를 때만) | dc | ② · `test_272_berm_source` · `test_472_3_calc_runner` |
| C5 | 소단 사면 점 | `common_util_cross_berm.ts` | `common_util_cross_berm.py` | dc · sc · co · ex | C1 과 한 셈 · `test_178_2_berm_fill_mirror` |
| C6 | 맹암거 얹기 · 기울임 | `common_util_structure_underdrain.ts` | `B06_Section_Engine_Tilt.py` | — | 짝 · 거울 시험 없음 |
| C7 | 반폭 맞춤 · 성토 5 m 경고 | `logic/B06_Section_UI_Cross_Fit.ts` · `logic/B06_Section_UI_Cross_FillSlope_Warn.ts` | — | ex(Fit) | 브라우저 |
| F1 | 시설 따라감(노선 바뀜) | `common_util_route_follow.ts:291` · `logic/B05_Profile_Pipeline_Follow.ts` · `logic/B05_Profile_Structure_Follow.ts` | `common_util_route_follow.py:120` | — | 짝 · `test_410_follow_xy` · `test_344_2_route_pairs` |
| F2 | 추가 측점 따라감 | `B05_Profile_Api_PlusStations.ts` followPlusDraft | `B05_Profile_Plus_Stations.py:82` | — | 짝 · `test_459_route_seat_mirror` |
| F3 | 시설 측점 계획 | `common_util_structure_station.ts` | `common_util_structure_station.py` | — | 짝 · `test_322_3_plan_mirror` |
| F4 | 형상 얹기(관 세트 · 벽 · 포장 그림 자료) — 관 = 지금 노선에 얹은 저장분(서버 `route_pipe_points` = 측점 계획 · 관 지점 GET 과 같은 이월) | `common_util_structure_attach.ts:130` | `B06_Section_Engine_Attach.py:114` | — | 짝 · `test_323_4_attach_mirror` · `test_472_d_pipe_route_attach` |
| F5 | 관 세트(관경 · 날개벽 · 집수정) | `common_util_culvert_sets.ts` · `logic/B06_Section_UI_Cross_Culvert_Const.ts` | `B06_Section_Engine_Culvert.py` | Const = sc · co · ex | 짝 · `test_b06_culvert_sets_mirror` · `test_b06_pipe_wall_height_mirror` |
| F6 | 벽 · 포장 | `common_util_structure_walls.ts` · `common_util_structure_pavement.ts` | `B06_Section_Engine_Structures_Wall.py` · `common_util_structure_pavement.py` | — | 짝 · `test_b06_structure_walls_mirror` · `test_b06_structure_pavement_mirror` |
| F7 | 관 길이 · 구조물 면적 · 다단 벽 | `B06_Section_Structure_Layouts.ts:95 · 287` · `design_structure_pipe_calc.ts` · `common_util_cross_structure_areas.ts` | — | sc | ② · `test_b06_pipe_length` · `test_299_ford_span_pipe` |
| F8 | 터파기 | `common_util_excavation.ts` | `common_util_excavation.py`(운영 부름 없음) | sc · co · ex | ② · `test_excavation_mirror` |
| F9 | 세월교 높이 · 깊이 | `design_structure_ford_calc.ts` | `B04_PreProcess_Router_Basins` ford_height_m · `B08_Quantity_Engine_Hydro` ford_depth | — | 짝 · `test_65_ford_defaults` · `test_73_11_b08_hydro_box_ford` |
| E1 | 누가토량 | `common_util_mass_haul.ts:426` | — | sc | ② · `test_309_mass_haul_badge` |
| E2 | 유토 배분 · 운반거리 | `common_util_mass_haul_balance.ts:584` | — | sc | 서버 Node 만(`/haul-plan`) |
| D1 | 배수유역 세부 나눔 | `logic/B05_Profile_Drainage_Live.ts`(일정만) | `common_util_drainage_detail.py` · `B04_PreProcess_Router_Basins.py:538` | — | 서버 |
| D2 | 관 자리 제안 | `logic/B05_Profile_Drainage_Suggest.ts`(모으기만) | suggest_pipe_positions | — | 서버 |
| D3 | 3D 길 모형 | `B05_Profile_UI_Corridor_Build.ts` · Worker | — | co | ②(`B05_Profile_Corridor_Prebuild.py`) |
| D4 | 평면 발자국 | `logic/B05_Profile_PlanFootprint.ts:160` · `logic/B05_Profile_Footprint_Stage.ts` | `B07_DesignDetail_Engine_Cad_PlanSlope.py`(B07 따로) | — | 브라우저 · `test_346_2_plan_footprint_mirror` |
| N1 | 노선 추천 | `B05_Profile_UI_RouteEdit_Initial_Model.ts`(보기만) | `B05_Profile_Engine_RouteInitial*` | — | 서버 · `test_419_1_recommend_engine` |
| N2 | 추천 후보 초기 종단(476) — [적용] 체인과 같은 조각을 메모리에서(노선 짜기 · 시설 따라감 · `build_sections` 438 · 켬 = 균형 얹기) · 저장본 `route_recommend/initial` · 화면 펴기 짝 = `flattenServerPlan` ↔ `draft_nodes` | 그리기만(476-2) · `logic/B05_Profile_UI_RouteEdit_Edits.ts` flattenServerPlan | `B05_Profile_Engine_InitialProfile.py` · `B05_Profile_Router_RouteInitial_Initial.py` | — | 서버 · [적용]은 체인이 다시 셈 · `test_476_1_recommend_initial`(체인 대조 0 mm) |
## 짝인데 값이 갈리는 자리
| # | 자리 | 브라우저 | 서버 | 처리 |
|---|---|---|---|---|
| X1 | 단면 모양 기본값 | `"left_cut"`(`B06_Section_Cross_Refresh.ts:438`) | `default_section_modes`(`B06_Section_Router_Design.py:402`) | 사용자 협의 · 손대지 않음 |
| X2 | 지반 표본 출처 · 입력 선 | 격자 `.bin` · `/route/plan` planned 선 | npz `sample_xy` · 저장 경로 폴리라인 | 노선 초안 없는 차례 = 정본 표본 유지로 막음 |
| X3 | 단면적 두 갈래 | 전부 TS | 모두 Node(472-4 ②) | 풀림 · 남은 차 = 설계선 점 반올림 동점 1e-4 m(파이썬 round 짝수 쪽 · TS 위쪽) |
| X4 | 평면 발자국 | TS PlanFootprint | B07 파이썬 PlanSlope | 거울 시험 |
| X5 | 경고 기준표 | `B05_Profile_UI_Panel_Fields.ts` 안 표 | `FOREST_ROAD_PROFILE_CRITERIA` | 거울 시험 · 자료 두 벌 |
| X6 | 노선 바뀜 넘기는 칸 | `B06_Section_Station_Keys.ts:46` 근처 | `B05_Profile_Route_Save.py:275-279` CARRY_INPUT_KEYS · ROUTE_CARRY_KEYS | 손으로 맞춘 목록 둘 · 거울 없음 |
+3
View File
@@ -0,0 +1,3 @@
3D 길 모형 — 종단 · 횡단으로 길 몸체를 지음.
- 노선 초안 · 종단 고침 동안 낡음 표시 · [저장] 뒤 서버 Node 가 다시 지음.
+5
View File
@@ -0,0 +1,5 @@
횡단 설계선 · 면적 계산 — 브라우저 즉시 · [저장] 때 서버 Node 정본.
- 설계선 = [[computeCrossDesign]] ↔ 파이썬 짝 · 단면적은 표준 · 구조물 측점 모두 Node.
- 다시 셈 입력 물러섬 = 세션 고름 → 저장 정본 설계 → config 기본값.
- 소단 구간 = 구조물에서 [[bermSpansFromStructures]] ↔ 서버 [[load_berm_spans]].
+5
View File
@@ -0,0 +1,5 @@
횡단 화면 — 카드 단추 · 조정창 · 반폭 · 표준횡단 · 상세 표.
- 카드 단추 = 세션 `crossdesign` 의 바꾼 칸만 · 메모리 상세 설계를 안 덮음.
- 조정창 칸 · 암선 · 암 경사 = 세션 칸이 값 주인(메모리 사본 없음) · 빈 칸 = 칸 지움.
- 표준횡단 = 저장분 · config 와 다를 때만 칸([[effectiveStandardCross]]).
+5
View File
@@ -0,0 +1,5 @@
관 · 배수 — 배수판 관 편집 · 배수유역 · 관 옵션.
- 관 주인 = 가게 `pipes` · 배수 편집기는 숨어도 가게를 따름.
- 관 자리 읽기 = 지금 노선에 얹은 관 지점 한 벌([[route_pipe_points]]) · 관 파일 누가거리를 그대로 읽지 않음.
- [저장] = 관 차례 `pipe_points` PUT · 관 옵션 예약 있으면 같은 파일 한 번 더.
+4
View File
@@ -0,0 +1,4 @@
누가토량 · 유토 배분 · 운반거리.
- 누가토량 = TS 한 벌(브라우저 · Node) · 그릴 때 셈(접혀도 셈).
- 유토 배분 = 서버 Node(`/haul-plan`) · 저장본 `_derived/haul` · 입력 지문 다를 때만 다시 셈.
+3
View File
@@ -0,0 +1,3 @@
평면 발자국 — 횡단 결과로 평면에 깎기 · 쌓기 테두리.
- 묶음 마지막 단계 · B07 은 따로(파이썬) · 거울 시험.
+4
View File
@@ -0,0 +1,4 @@
측점마다 원지반 표본 — 브라우저 지형 격자 · 서버 npz.
- 표본 짝 = 브라우저 격자 ↔ 서버 `sample_xy`(거울 시험) · 입력 선이 달라 mm 차.
- 노선 초안 없는 차례 = 정본 표본 유지 · 넓힘(표본 폭 넘음) = 저장 밖 서버.
+6
View File
@@ -0,0 +1,6 @@
계산 묶음 · 설계 가게 · 상세 가게 — 입력 → 파생 한 방향.
- 묶음 단계 = 노선 → 따라감 → 측점 → 지반 → 첫 계획선 → 종단 → 횡단 → 발자국 · 차례 번호 · 옛 차례 버림.
- 묶음 결과 내보냄 = [[publishPagePipeline]] · 조용한 차례 끝 = 가게 측점 = 지금 상세 측점.
- 설계 가게 = [[useDesignStore]] · 멈춘 뒤 1 s 세션 · 상세 가게 = [[loadSectionDetail]] · 새로고침 = [[checkSessionCanon]].
- 서버 Node 입구 = 계산 등록부 id → 함수 · 파이썬은 [[run_calc]].
+6
View File
@@ -0,0 +1,6 @@
종단 계획고 — ▲▼ · 직선화 · 자유지정 · 쉬프트 · 종단 R · 시종점 · 페이지 설정.
- 편집값 주인 = 가게 `alignmentDraft`(세션 `alignment-draft`) · 지금 값 = 초안 ?? 서버 저장분.
- 계획선 짓기 [[buildAlignment]] ↔ 파이썬 짝(거울 시험).
- 짝 있는 옮긴 측점 = 꼭짓점 따라감([[followMovedNodes]]) · 짝 없는 새 측점 = 꼭짓점 안 세움.
- [저장] = 편집 델타만 보내고 서버가 종단 다시 셈.
+4
View File
@@ -0,0 +1,4 @@
추천 노선 후보를 서버가 찾고 화면이 보여 줌 · 고른 후보를 [적용].
- 후보 초기 종단 = [적용] 체인과 같은 조각을 메모리에서 셈 · 저장본 `route_recommend/initial`.
- [적용] = 저장 밖 · 즉시 [[replan_route]] · 종단 = 새 규칙 초기 종단 · 옛 손 입력 안 넘김 · 초기값 다시 뜸 · 초안 전부 지움.
+5
View File
@@ -0,0 +1,5 @@
평면 노선 편집기 — 교점 끌기 · 방향키 · 교점 추가 · 삭제 · 곡선 R · L · 스냅.
- 조작은 편집기 메모리 → 놓을 때 세션 `route-draft` · 첫 초안이면 초안 전 상세를 hold.
- 곡선 선 = [[buildEditedPolyline]] ↔ 파이썬 짝 [[build_planned_polyline]] · 선 자리 x · y 4자리 한 벌.
- 노선 초안 차례 = 새 · 옮긴 측점만 브라우저 지반 표본.
+5
View File
@@ -0,0 +1,5 @@
노선이 바뀐 [저장] — 새 노선 짜기 · 관 · 구조물 · 추가 측점 따라감 · 횡단 고침 넘김.
- 서버 [[run_route_save_chain]] = 노선 · 측점 · 표본 · 횡단을 처음부터 · 전부 아니면 없음.
- 관 · 구조물 초안 다시 얹기 = [[rebasePipeDraft]] · [[rebaseStructureDraft]](좌표 따라감).
- 횡단 고침 넘김 = [[carryKeyedStates]] ↔ 서버 [[carry_cross_inputs]] · 자리 칸은 같은 자리([[sameSeat]]) 측점만.
+5
View File
@@ -0,0 +1,5 @@
[저장] · [확정] · [초기화] · 떠남.
- [저장] · [확정] = 한 본문 한 요청 · 서버가 차례(노선 → 설정 → 관 → 구조물 → 추가 측점 → 종단 → 관 옵션 → 상단측 → 횡단)로 · 하나라도 실패 = 되돌림.
- 횡단 패치 = [[crossPatchesFromCache]] · 노선 바뀐 [저장] = [[routeCarryInputs]] 를 같은 본문에.
- [폐기] = 세션 고침 칸만 지움 · 메모리는 떠남이 비움 · [초기화] = DB 노선 행 지우고 초기값으로.
+4
View File
@@ -0,0 +1,4 @@
페이지 틀 · 보기 전환 · 패널 · 고름.
- B05 ↔ B06 전환 = 보기 숨김 · 보임만 · 보인 보기가 가게 · 세션을 다시 읽음 · 묶음은 숨은 동안도 셈.
- 접기 = 그리기만 건너뜀 · 팝아웃 = 같은 함수 · 같은 가게.
+3
View File
@@ -0,0 +1,3 @@
서버 창구 · 저장소 · 스키마 — 브라우저 요청을 받아 DB · 정본 파일을 읽고 씀.
- 정본 쓰기 = [저장] · [확정] · [초기화] · 저장 밖 셋만.
+5
View File
@@ -0,0 +1,5 @@
측점 목록 · 열쇠 · 갈아끼움 — 노선 · 시설이 바뀌면 측점을 다시 세움.
- 열쇠 = [[common_util_station_key#stationKey]] 한 벌(파이썬 짝 · 거울 시험).
- 갈아끼움 [[swapSectionStations]] — 노선 초안 없는 차례 = 서버 정본 측점 기하 · 표본 유지(같은 자리 0.01 m · 축 1e-3).
- 지금 상세에 없던 측점이 정본 자리로 돌아옴(관 지움 ↶) = 정본 설계.
+5
View File
@@ -0,0 +1,5 @@
구조물 — 추가 · 옮김 · 지움 · 형상 · 소단 · 시설 줄.
- 주인 = 가게 `structures` · 화면 사본은 가게 알림으로 맞춤([[announceDesignStore]]).
- 시설 줄 측점 = 종류 표 · 구조물 · 관 알림이 다 온 뒤만 심음(모름 ≠ 빈 목록).
- 소단 사본 쓰는 자리 = 시설 줄 하나 · 같으면 안 씀.
-74
View File
@@ -1,74 +0,0 @@
---
type: concept
status: stable
related_pages: ["[[design_page/operations_b05]]", "[[design_page/operations_b06]]", "[[design_page/operations_common]]", "[[design_page/values]]", "[[design_page/session_keys]]", "[[design_page/calculations]]", "[[design_page/rules]]", "[[design_data_lifecycle]]", "[[workflow_state]]"]
last_updated: 2026-10-19
source: ["docs/raw/조사/2026-10-19_470_B05B06_데이터흐름.md", "docs/raw/조사/2026-10-19_472_구조다시세움_목록.md", "docs/raw/조사/2026-10-19_465_요구272_값안변함.md", "CLAUDE.md §5"]
---
# B05 · B06 한 페이지 데이터 흐름
- 대상 — `A00_Common/design/` · `B05_Profile/` · `B06_Section/`(주소 둘 `#/b05-profile` · `#/b06-section` = 페이지 하나).
- ⛔ B05 · B06 고치기 전 이 폴더를 읽음 · 조작 · 값 · 계산 흐름을 바꾸면 **같은 커밋에** 해당 표를 고침.
- 파일 이름만 적음 — `design_*` = `A00_Common/design/` · `B05_Profile_*` = `B05_Profile/` · `B06_Section_*` = `B06_Section/` · `common_util_*` = `common_util/` · `logic/…` = 그 폴더의 `logic/`.
- 「사본 수」 = 같은 값을 쥔 자리 수(메모리 · 가게 · 세션 · 서버) — 줄일수록 좋음.
## 문서
| 문서 | 내용 |
|---|---|
| [[design_page/operations_b05]] | B05 평면 · 시설 · 종단 조작 |
| [[design_page/operations_b06]] | B06 횡단 카드 · 조정창 · 저장 밖 조작 |
| [[design_page/operations_common]] | ↶↷ · 보기 전환 · 접기 · 팝아웃 · 새로고침 · 떠남 · [저장] · [확정] · [초기화] |
| [[design_page/values]] | 값마다 주인 · 사본 · 셈 자리 · 맞추는 때 |
| [[design_page/session_keys]] | 세션 열쇠 등록부 · [저장] 차례 |
| [[design_page/calculations]] | 계산 40 갈래 · TS · 파이썬 짝 · Node 묶음 · 실행 자리 |
| [[design_page/rules]] | 지킬 규칙 · 바꿀 때 할 일 · 남은 틈 |
## 층 그림
```
사용자 조작(끌기 · 단추 · 칸)
▼
① 화면 메모리 — 편집기 · 배수판 · 보기 구조물 · 시설 줄
② 상세 가게 — Section_Store cache(페이지 수명) · 정본 사본 canon(세션)
③ 설계 가게 design_store — stations · profile · footprint 는 메모리 우선(멈춘 뒤 1 s 세션)
④ 세션 sessionStorage — aislo:{통}:{이름}:{프로젝트}[:{노선}] · draft · result · pref(localStorage)
⑤ 계산 묶음 — route → follow → stations → ground → planline → profile → cross → footprint
‖ 뒤: drainage(서버 배수유역) · corridor(3D 낡음)
│ 뒤에서(표시용) │ [저장] · [확정] · [초기화] · 저장 밖 셋
▼ ▼
⑥ 서버 파생 저장본 ⑦ 서버 작업본(정본) — DB · 정본 파일
_derived/haul · route_recommend · ▲ [초기화] = 덮어씀
04_detail_cache.pkl ⑧ 초기값 initial_snapshot/
```
## 층 표
| 층 | 위치(파일:줄) | 역할 · 비우는 때 |
|---|---|---|
| ① 화면 메모리 | `B05_Profile_UI_RouteEdit.ts:124-145` · `B05_Profile_UI_Drainage_Panel.ts:123 · 142` · `B05_Profile_UI_Page_Structures.ts:78` · `B06_Section_UI_Page_Structures_Panel.ts:175-176` · `design_facility_lane.ts:95-105` | 조작 즉시 · 페이지 dispose 로 버림 |
| ② 상세 가게 | `B06_Section_Section_Store.ts:68` cache · `:69` pending · `:117` loadSectionDetail · `:431` peekSectionDetail | 상세 횡단(살아 있는 객체) · 떠남 `forgetSectionMemory`(`:424`) · 정본 바뀜 `invalidateSectionDetail`(`:403`) |
| ② 정본 사본 · 상세 칸 | `B06_Section_Section_Canon.ts:36` keepCanon · `:59` sessionDetail · `:72` savedDesigns · `:84` forgetCanon · `:107-143` 칸 셋(저장 정본 · 초안 전 hold · 노선 초안 · 472-5) | 서버 정본 설계 · 자리 · 표본 · invalidate 때 비움 · 칸 셋 = 떠남 · invalidate 때 비움 |
| ③ 설계 가게 | `design_store.ts:226` useDesignStore · `:151` 메모리 칸 · `:160` 1 s | 읽고 쓰는 창구 · 쓰면 듣는 쪽 부름 |
| ④ 세션 | `A00_Common/b_page_state.ts` 등록부 | draft = 사용자 값 · result = 계산 · 서버 사본 · [[design_page/session_keys]] |
| ⑤ 계산 묶음 | `logic/design_pipeline.ts:33` 단계 · `:44` 뒤 단계 · `:49` STAGE_DEPS · `design_pipeline_page.ts:187-192` dispose | 차례 번호 · 옛 차례 버림 · 입력 대 묶음 맞춤 최대 3 바퀴(`:116`) |
| ⑤ 묶음 → 가게 | `design_pipeline_page_publish.ts` publishPagePipeline · shown · opened · recheck | 측점 · 종단 · 발자국 냄 · 조용한 차례 = 상세 측점 |
| ⑥ 서버 파생 | `_derived/haul/*` · `B05_Profile/route_recommend/*` · `04_detail_cache.pkl` | 입력 지문 다를 때만 다시 셈 · 지워도 다시 셈 |
| ⑦ 서버 작업본 | DB `routes` · `route_points` · `route_statistics` · `longitudinal_sections` · `cross_sections` · 파일 `pipe_points.json` · `structures.json` · `plus_stations.json` · 노선 파일 · 곡선표 · 종단 파일(`mass_haul`) · 3D corridor | [저장] · [확정] · [초기화]만 씀(예외 = [[design_page/rules]]) |
| ⑧ 초기값 | `initial_snapshot/` · `common_util_initial_snapshot.py:38-54 · 266-327` | 자동설계 체인 · 추천 [적용] 직후만 씀 · [초기화]가 읽음 |
## 묶음 단계
| 단계 | 기대는 값(STAGE_DEPS) | 모듈 | 낳는 값 |
|---|---|---|---|
| route | input.route | `logic/B05_Profile_Pipeline_RouteGround.ts` | 노선 선 · 곡선 |
| follow | route | `logic/B05_Profile_Pipeline_Grade.ts` · `common_util_route_follow.ts` | 관 · 구조물 · 계획고 · 추가 측점 따라감 |
| stations | route · pipes · structures · plusStations | `logic/design_pipeline_stations.ts` | 측점 목록 · 열쇠 |
| ground | stations | `logic/B05_Profile_Pipeline_RouteGround.ts` | 측점 지반 표본 |
| planline | ground | `logic/B05_Profile_Pipeline_Grade.ts` | 첫 계획선 base PVI |
| profile | planline · alignment | `logic/B05_Profile_Pipeline_Grade.ts` | 측점 계획고 |
| cross | profile · ground · cross · pipes · structures | `logic/B06_Section_Cross_Stage.ts` · 저장 노선 = `B06_Section_Cross_Refresh.ts` | 횡단 설계선 · 면적 |
| footprint | cross · ground | `logic/B05_Profile_Footprint_Stage.ts` | 평면 발자국 |
| drainage(뒤) | route · pipes | 배수판 `pipelineStage` → 서버 | 배수유역 |
| corridor(뒤) | profile · cross | `design_pipeline_page.ts:355-358` | 3D 낡음 표시 |
-44
View File
@@ -1,44 +0,0 @@
---
type: concept
status: stable
related_pages: ["[[design_page/index]]", "[[design_page/operations_b06]]", "[[design_page/operations_common]]", "[[design_page/values]]", "[[design_page/rules]]"]
last_updated: 2026-10-19
source: ["docs/raw/조사/2026-10-19_470_B05B06_데이터흐름.md", "docs/raw/조사/2026-10-19_465_요구272_값안변함.md"]
---
# B05 조작 흐름 — 평면 · 시설 · 종단
- 「묶음 전 단계」 = route → … → footprint 한 바퀴 + 가게 측점 냄 → 상세 갈아끼움(`swapSectionStations` `B06_Section_Section_Store.ts:230`) → 누가토량 다시 그림.
- 갈아끼움 표본 규칙 = 노선 초안 차례면 새 · 옮긴 측점만 브라우저 표본 · 아니면 정본 기하 · 표본 그대로([[design_page/rules]]).
- 「[저장] 때」 = 서버가 입력으로 정본을 다시 셈 · 브라우저 결과를 받아 적지 않음.
## 평면(노선)
| 조작 | 처음 놓이는 곳 | 브라우저 즉시 셈(모듈) | 서버 뒤 셈(API) | [저장]·[확정] 때 | 사본 | 위치(파일:줄) |
|---|---|---|---|---|---|---|
| 교점 끌기 | 편집기 메모리(planned · curveRadius · curveArc) → 놓을 때 세션 `route-draft` · 첫 초안이면 초안 전 상세를 hold | 묶음 전 단계(노선 초안 차례 · 새 표본) | 지반 대체 `/route/elevations` · 배수 `/drainage/live` · 유토 배분 `/haul-plan` | `/design/save` `route` 칸 — 서버가 노선 · 측점 · 표본 · 횡단 처음부터 · 관 · 구조물 다시 얹기 · 세션 횡단 고침 넘김(Node) · 뒤 차례 = 새 노선 · 전부 아니면 없음 | 11 | `B05_Profile_UI_RouteEdit.ts:126-145` · `B05_Profile_UI_RouteEdit_Pointer.ts:387-430` · `design_pipeline_page.ts:636-659` · `B05_Profile_UI_RouteEdit_Apply.ts:94-135` → `B06_Section_Router_DesignSave.py:128-145` → `B05_Profile_Router_Replan.py:552-605` → `B05_Profile_Route_Save.py:337-510` |
| 방향키(0.1 m · Shift 1 m) | 교점 끌기와 같음 | 같음 | 같음 | 같음 | 11 | `B05_Profile_UI_RouteEdit_Keys.ts:61-64 · 118-159` |
| 교점 추가 · 삭제 | 편집기 메모리 → `route-draft` | 묶음 전 단계 · 측점 수 바뀜 → 갈아끼움 | 같음 | 같음 | 11 | `B05_Profile_UI_RouteEdit_Input.ts:149-158` · `logic/B05_Profile_UI_RouteEdit_Edits.ts:410-426` |
| 곡선 R · L | 편집기 `curveRadius` · `curveArc` → `route-draft` | 묶음 전 단계 · 확폭 · MC 이동 → 곡선 열쇠 옮김(`curveMoves`) | 같음 | `/design/save` `route` 칸 — 넣은 R · L 로 서버가 같은 규칙 | 11 | `B05_Profile_UI_RouteEdit_CurveBar.ts:76-162` · `logic/B05_Profile_Sections_Stations.ts:332-337` · `B05_Profile_Router_Replan.py:582 · 599-600` |
| 스냅 켬 · 간격 | 간격 = pref `routeedit-snap-step` · 켬 = `route-draft.stationSnap` | 켬 = 곡선 IP 를 옮김(한 걸음) → 묶음 전 단계 | — | 곡선표 파일 · 서버는 측점만 맞춤(IP 맞춤은 브라우저만) | 3 | `B05_Profile_UI_RouteEdit_SnapStep.ts:74-78` · `logic/design_pipeline_stations.ts:52-59` · `B05_Profile_Engine_Sections_Core.py:457-464` |
| 추천 노선 [적용] | result `routeedit-initial` → `route-draft` | 묶음 한 바퀴 | 미리 셈 `/route/initial` → `route_recommend/*` | **저장 밖** — 바로 `/route/replan`(as_initial) · 종단 = 새 규칙 초기 종단 · 횡단 = 기본 설계(옛 계획고 · 편집 · L · 횡단 손 입력 안 옮김 · 476 · 요구 277) · [저장]과 같은 잠금 · 초기값 다시 뜸 · 초안 전부 지움 · 페이지 다시 세움 | 11 | `B05_Profile_UI_RouteEdit_Initial.ts:139 · 161-181` · `B05_Profile_UI_RouteEdit_Apply.ts:211 · 220-245` · `B05_Profile_Router_Replan.py:659-662` |
## 시설(관 · 구조물 · 추가 측점)
| 조작 | 처음 놓이는 곳 | 브라우저 즉시 셈(모듈) | 서버 뒤 셈(API) | [저장]·[확정] 때 | 사본 | 위치(파일:줄) |
|---|---|---|---|---|---|---|
| 구조물 추가 · 옮김 · 지움 | 보기 메모리(B05 `ownStructures` · B06 `structures`) → 시설 줄 `set` → 세션 `structures` | 시설 줄(알약 · 비정규 측점 · 소단 사본 `berm`) · 묶음 stations → 갈아끼움(정본 표본 유지) · 짝 있는 옮김만 종단 꼭짓점 따라감 · 짝 없는 새 측점 = 꼭짓점 안 세움 · cross 면적 | — | `PUT /route/structures`(초안 그대로) → 횡단 차례 측점 세움 · 면적 Node | 11 | `design_shared.ts:303-322` · `B05_Profile_UI_Page_Structures.ts:98-118` · `design_facility_lane.ts:209-217` · `common_util_structure_station.ts:356` · `B05_Profile_Structures_Router.py:386-405` |
| 관 추가 · 옮김 · 지움(B05 배수판) | 배수판 메모리(`facilityStore` · `pipeEditor`) → 세션 `pipes` · `pipe-ids` | 구조물과 같음 · 관 길이 다시 셈 · 일반 측점 자리 관 지움 = 갈아끼움 · 관 지움 ↶ = 그 측점 정본 설계 · 관 초안 없는 걸음으로 ↶ = 저장분(배수판 · 시설 줄 · 알약 · 목록 · 배수유역도 함께 · 472 덧) · 열린 관 폼은 ↶ ↷ 로 옮겨진 관을 따라감(`B05_Profile_UI_Structures_Panel.ts` setPipeFacilities) | 배수유역 `/drainage/detail-basins`(저장 안 함) | `PUT /drainage/pipe-points`(관 차례) · 관 옵션 예약 있으면 같은 파일 한 번 더(관 옵션 차례) · 서버 유역 · 측점 세움 | 14 | `B05_Profile_UI_Drainage_Panel.ts:150-153 · 405-421 · 574-655` · `B05_Profile_Api_Pipes_Draft.ts:30-37` · `B04_PreProcess_Router_Basins.py:538 · 574-633` |
| 추가 측점 | 세션 `plus-stations` | 시설 줄 · 묶음 stations · 갈아끼움 · 미리보기 `/section/preview-stations` · 꼭짓점 안 세움 | — | `PUT /section/plus-stations` · 서버 x · y · 측점 세움 | 5 | `B05_Profile_UI_Profile_CrossModal.ts:205-246` · `B05_Profile_Api_PlusStations.ts:44-76 · 128-145` · `B06_Section_Router_Stations.py:428-472` |
## 종단(계획고)
| 조작 | 처음 놓이는 곳 | 브라우저 즉시 셈(모듈) | 서버 뒤 셈(API) | [저장]·[확정] 때 | 사본 | 위치(파일:줄) |
|---|---|---|---|---|---|---|
| 계획고 ▲▼ | 가게 `alignmentDraft` → 세션 `alignment-draft`(지금 값 = 초안 ?? 가게 `alignmentSaved` · 모듈 사본 없음) | 종단선 · 묶음 profile → cross(전 측점 횡단 · 면적) → 누가토량 · 알약 | 유토 배분(400 ms 뒤 · Node · `_derived/haul`) | `PUT /route/profile-alignment`(편집 델타) · 파이썬 종단 다시 셈 · 횡단 차례 Node 면적 · 누가토량 | 8 | `design_profile.ts:311-344` · `B05_Profile_UI_Profile_Edit.ts:113-258` · `logic/B05_Profile_UI_Profile_Alignment.ts:566-603` · `B05_Profile_Router.py:416-500` → `B05_Profile_Engine_Grade_Profile.py:537-575` |
| 직선화 | ▲▼ 와 같음(R = L/Δ 로 적음) | ▲▼ 와 같음 · 실시간 띠 | 같음 | 같음 | 8 | `B05_Profile_UI_Profile_Panel_Tools.ts:133-158` · `logic/B05_Profile_UI_Profile_Straighten.ts` · `logic/B05_Profile_Profile_CurveLaw.ts:21` |
| 자유지정 | 그리기 자체 되돌리기 → 끝나면 ▲▼ 와 같음 | 같음 | 같음 | 같음 | 8 | `logic/design_profile_free.ts:127-170 · 224-313` |
| 쉬프트(직선 구간 올림 · 내림) | ▲▼ 와 같음 | 같음 · 붉은 강조 | 같음 | 같음 | 8 | `B05_Profile_UI_Profile_Tools.ts:111-114 · 216-217` |
| 종단 R(L 칸) | `curve_radii` → `alignment-draft` | 같음 | 같음 | 같음 · 서버는 R 로 받음 | 8 | `B05_Profile_UI_Profile_Table.ts:360-413` · `logic/B05_Profile_UI_Profile_Alignment.ts:685-695` |
| 시 · 종점 계획고 | `alignment-draft`(끝 측점 ▲▼) | 같음 | 같음 | 같음 | 8 | `B05_Profile_UI_Panel_Settings.ts:113-133` · `logic/B05_Profile_EndOffsets.ts:43` |
| 페이지 설정(설계속도 · 지형 · 기준 · 최소고 강제) | 가게 `pageSettings` → 세션 `pagesettings`(최소고 강제 = 편집 가드가 이 칸을 바로 읽음) | 최대 기울기만 종단에 얹음 · 횡단 안 셈 · ↶ ↷ 뒤 가게가 폼 · 기울기 한도 · 시작 측점을 다시 부름 | — | `PUT /route/page-settings`(설정 차례) · 서버는 경고만 다시(기하 그대로) | 4 | `B05_Profile_UI_Panel.ts:130-142` · `B05_Profile_Api_PageSettings.ts:47-85` · `B05_Profile_UI_Page_Latest.ts:92-128` · `design_profile.ts:645` · `design_view_profile.ts:355` · `B05_Profile_PageSettings.py:79-107` |
-36
View File
@@ -1,36 +0,0 @@
---
type: concept
status: stable
related_pages: ["[[design_page/index]]", "[[design_page/operations_b05]]", "[[design_page/operations_common]]", "[[design_page/values]]", "[[design_page/rules]]"]
last_updated: 2026-10-19
source: ["docs/raw/조사/2026-10-19_470_B05B06_데이터흐름.md", "docs/raw/조사/2026-10-19_465_요구272_값안변함.md"]
---
# B06 조작 흐름 — 횡단 카드 · 조정창 · 저장 밖 조작
- 「횡단 다시 셈」 = 묶음 cross 강제(`forceCross` · `pipelineCross`) → 저장 노선은 `refreshLocally`(`B06_Section_Cross_Refresh.ts:330`) 전 측점 `computeCrossDesign` · 면적 · 누가토량.
- 다시 셈 입력 물러섬 = 세션 고름 → 저장 정본 설계(`savedDesigns` `B06_Section_Cross_Refresh.ts:402-408`) → config 기본값.
- 횡단 [저장] 한 요청 = `/sections/{노선}/save` 패치(`crossPatchesFromCache` `B06_Section_Section_Store.ts:449`) → 파이썬 설계선 다시 · Node 면적(`B06_Section_Router_Confirm.py:174-187 · 275-357` → `B06_Section_Server_Calc_Prebuild.py:232-384` → `B06_Section_Server_Calc_Node.ts:94-135`).
## 카드 · 조정
| 조작 | 처음 놓이는 곳 | 브라우저 즉시 셈(모듈) | 서버 뒤 셈(API) | [저장]·[확정] 때 | 사본 | 위치(파일:줄) |
|---|---|---|---|---|---|---|
| 카드 단추(지반 · 단면 · 측구 · 포장 · 2단) | 세션 `crossdesign` 만 · 바꾼 칸만(앞서 바꾼 칸에 얹음 · 안 바꾼 지반 · 단면을 안 박음) · 메모리 상세 설계 안 덮음 | 횡단 다시 셈 · 카드 = 다시 셈 알림(`onCrossDesignsUpdated`)이 그림 | — | 횡단 패치 · 파이썬 설계선 다시 | 5 | `B06_Section_UI_Cross_Design.ts:278-289` · `B06_Section_UI_Page_Design_Sync.ts:59-104` · `B06_Section_Cross_Design_Session.ts:44-52` |
| 설계선 쉬프트(노견 확장) | 세션 `shoulderext` | 횡단 다시 셈 | — | 횡단 패치 | 5 | `B06_Section_UI_Cross_Design_Pick.ts:402` · `B06_Section_UI_Page_Design_Sync.ts:71-76` · `B06_Section_Cross_Refresh.ts:393-397` |
| 소단 구간(구조물) | 구조물 → 시설 줄 `syncBerm` 이 세션 `berm`(같으면 안 씀 · 쓰면 cross 한 번 · B05 만 열어도) | 횡단 다시 셈 · 사본 없으면 저장분 `design.berm` | — | 서버가 `structures.json` 에서(`load_berm_spans`) | 6 | `design_facility_lane.ts:151-166` · `B06_Section_Cross_Refresh.ts:362-368 · 521-522` · `B06_Section_Engine_Structures_Wall.py:124` |
| 소단 깊이 ▲▼ | 세션 `bermdepth` | 횡단 다시 셈 | — | 횡단 패치 | 5 | `B06_Section_UI_Cross_Design_Pick.ts:408` · `B06_Section_UI_Page_Design_Sync.ts:78-83` · `B06_Section_Cross_Refresh.ts:513-519` |
| 암선 · 암 경사 | 세션 칸 `rockb` · `cutslope`(값 주인 · 읽을 때마다 칸 · 메모리 사본 없음 · ↶ ↷ 가 칸을 바꾸면 카드 · 면적 다시) | 횡단 다시 셈(암 측점 2단 무릎 · 면적) | — | 횡단 패치 · 파이썬 설계선 | 5 | `B06_Section_UI_Cross_Line_Cell.ts:161 · 166` · `B06_Section_UI_Cross_CutSlope.ts:94 · 114` · `B06_Section_Cross_Refresh.ts:229-243 · 306` |
| 조정창(흙막이 · 집수정 · 유입 · 경간 · 세월교 · BOX · 연동) | 세션 칸(`revetx` · `revetlink` · `basinadjust` · `inletstruct` · `extrawall` · `extraspan` · `fordadjust` · `boxadjust` · 값 주인 · 메모리 사본 없음 · 창구 = b_page_state 열쇠 `B06_Section_Session_Cells`) · 빈 칸 = 칸 지움 · 관 옵션 = `culvertopt` | 카드 다시 · 구조물 면적 · 누가토량 · ↶ ↷ 가 칸을 바꾸면 상세 얹기(고치기 전 칸 + 지금 칸) · 카드 · 면적 다시 | — | 횡단 패치 · Node 구조물 면적 · 관 옵션 = 관 옵션 차례 `pipe_points` PUT | 6 | `B06_Section_UI_Page_Station_Controls.ts` · `B06_Section_Session_Cells.ts` · `B06_Section_Section_Adjusts.ts` noteAdjustBase · withDraftAdjusts · `design_view_cross.ts` onRestore · `logic/B06_Section_UI_Page_Patches.ts:41-136` |
| 반폭 ◀▶ | 측점 = 세션 `crossw` · 전역 = `cross-display` | 표시만 · 횡단 전체 강제 다시 셈(헛일) | — | 측점 반폭만 패치 · `cross-display` 안 감 | 3 | `B06_Section_UI_Cross_View.ts:664-670` · `design_view_cross.ts:251-263 · 448-457` · `B06_Section_Display_Width.ts:20-23` |
| 표준횡단 · [전체 반영] | 세션 `std-cross` — 저장분(없으면 config)과 다를 때만 · 열기만 = 칸 없음 · 계산 = 세션 → 저장분 → config([폐기]에도 안 지움) | 전 측점 다시 셈(지문에 표준 포함) | — | `standard_cross_section` 으로 · 서버 설계선 다시 | 3 | `B06_Section_UI_Standard_Panel.ts:303-325 · 450-458` · `B06_Section_Api_Fetch.ts:101-111` · `B06_Section_Router_Confirm.py:157-162` |
## 시설 · 평면 · 저장 밖
| 조작 | 처음 놓이는 곳 | 브라우저 즉시 셈(모듈) | 서버 뒤 셈(API) | [저장]·[확정] 때 | 사본 | 위치(파일:줄) |
|---|---|---|---|---|---|---|
| B06 폼 관 측점 · 옵션 고침 | `pipeFacilities` → 세션 `pipes` · 옮긴 자리(정수 m)로 옵션 · 캐시 얹기 · 집계 찾음 | B05 관 옮김과 같음 | 배수유역 다시 나눔 | 관 차례 `PUT /drainage/pipe-points` | 14 | `B06_Section_UI_Page_Structures_Panel.ts:300-345 · 536-560` |
| B06 구조물 패널 | 보기 `structures` → 시설 줄 `set` → 세션 `structures` | B05 구조물과 같음 | — | `PUT /route/structures` | 11 | `B06_Section_UI_Page_Structures_Panel.ts:211-227 · 582-598` |
| B06 평면 패널 끌기 | B05 교점 끌기와 같음(같은 편집기를 옮겨 붙임) | 같음 · B06 쪽 갈아끼움이 먼저 | 같음 | `/design/save` `route` 칸 | 11 | `design_plan_peek.ts:61-77` · `B05_Profile_UI_RouteEdit.ts:290-293 · 599-601` · `B06_Section_UI_Page_Station_Swap.ts:35` |
| 넓힘(표본 폭 넘음) | 없음 — 확인창 뒤 바로 서버 | 응답 뒤 상세 강제 다시 받음 | `/sections/{노선}/regenerate` | **저장 밖** — DB 바로 씀(이미 정본) | 3 | `B06_Section_UI_Page_Common.ts:32-63` · `B06_Section_Router.py:378-388` · `B06_Section_Router_Widen.py:80` |
| [측점 만들기] | 없음 — 바로 서버 | 응답 뒤 상세 비움 · 강제 다시 받음 · 알림 | `/section/missing-stations` | **저장 밖** — 종단 · 횡단 파일 바로 씀 | 3 | `B06_Section_UI_Missing_Stations.ts:86-138` · `B06_Section_Router_Stations.py:236-296 · 365-407` |
-43
View File
@@ -1,43 +0,0 @@
---
type: concept
status: stable
related_pages: ["[[design_page/index]]", "[[design_page/operations_b05]]", "[[design_page/operations_b06]]", "[[design_page/session_keys]]", "[[design_page/rules]]"]
last_updated: 2026-10-19
source: ["docs/raw/조사/2026-10-19_470_B05B06_데이터흐름.md", "docs/raw/조사/2026-10-19_465_요구272_값안변함.md", "PLAN.md 466 · 467 · 468"]
---
# 공통 조작 흐름 — 되돌리기 · 보기 · 수명 · 저장
## 조작 표
| 조작 | 처음 놓이는 곳 | 브라우저 즉시 셈(모듈) | 서버 뒤 셈(API) | [저장]·[확정] 때 | 사본 | 위치(파일:줄) |
|---|---|---|---|---|---|---|
| ↶ ↷ | 사진첩 = 세션 draft 칸 글(고름 · 관 id 등 뺌) · 빈 칸 「""」·「{}」·「null」 = 칸 없음 · 「[]」 = 값 | 칸 되씀 → 종단 · 시설 · 노선 다시 먹임 · 횡단 입력 물러섬 = 저장 정본 · 관 초안 없는 걸음 = 저장분(배수판이 마지막 저장분 답과 견줘 다르면 다시 읽음) · ↶ ↷ 켜짐 = 쌓임 · 되돌림 알림(`change`)으로 바로 | — | — | 사진 n | `B05_Profile_UI_Profile_History.ts:50 · 57 · 186 · 226-245` · `design_pipeline_facilities.ts:62-69` · `B05_Profile_UI_RouteEdit_History.ts:174-180` · `B05_Profile_UI_Profile_Panel_Tools.ts` restorePipes · `B05_Profile_UI_Drainage_Panel.ts` setPipeChainages |
| B05 ↔ B06 전환 | 없음(보기 숨김 · 보임) | 보인 보기가 가게 · 세션 다시 읽음 · 입력 대 묶음 맞춤(최대 3 바퀴) · 시설 줄 · 묶음은 숨은 동안도 셈 | — | — | — | `design_page.ts:209-262` · `design_pipeline_page.ts:559-582` · `design_view_cross.ts:557-565` · `design_view_profile.ts:427` |
| 종단 판 접기 | pref `profile-collapsed` | 본문 0 크기 = 파생값(낡음 예약 · 유토 총괄값 · 배분 선반입 · 측점 자료)만 셈 · 그리기만 건너뜀 | — | — | — | `B05_Profile_UI_Profile_Render.ts:131-159 · 211-226` · `B05_Profile_UI_Profile_Drag.ts:43-58` |
| 팝아웃 | 없음 — 종단 판 DOM 만 새 창 | 같은 함수 · 같은 가게 | — | — | — | `design_profile_popout.ts:87-132` |
| 새로고침 | 세션 남음 · 메모리 전부 잃음 | 세션 상세로 먼저 섬 → 서버 정본과 비교(`checkSessionCanon`) → 다르면 갈아씀 · 노선 초안 있으면 hold · 관 · 구조물 · 추가 측점 초안 있으면 측점 냄 · 없으면 조용히 열고 가게 측점 = 상세 측점 | 상세 GET(정본 확인) | — | — | `B06_Section_Section_Store.ts:117-135` · `design_pipeline_page.ts:528-545` · `design_pipeline_page_publish.ts` opened · shown |
| 떠남 [취소] | 없음 — 페이지 그대로 | — | — | — | — | `design_leave.ts:95-123` |
| 떠남 [폐기] | 세션 고침 칸 지움(모든 노선 · `NOT_EDITS` · B08 · B09 칸 뺌) → 떠남 = dispose | dispose = 묶음 · 시설 줄 · 페이지 자료(`forgetPageData`) · 상세 메모리(`forgetSectionMemory`) 버림 · 다시 열면 새로고침 길 | — | — | — | `design_leave.ts:27-42 · 85-87` · `design_page.ts:286-295` · `B06_Section_Section_Store.ts:424-428` |
| [저장] | — | 앞 단계 `prepare` = 전 측점 강제 다시 셈(서버가 면적 버림) · 화면 잠금 | 조각 한 본문 `POST /design/save`(서버가 아래 차례 · 전부 아니면 없음 · 노선 바뀜도) · 서버 정본 다시 셈 | 끝 = `invalidateSectionDetail` · 두 보기가 새 상세 한 객체 받음 · 노선 바뀜 = 페이지 다시 세움 | — | `design_shared.ts:395-448` · `design_save.ts:183-237` |
| [확정] | — | [저장]과 같음 | [저장] + 빈 측점 기본 설계 · 단계 완료 · 측구 쪽을 B05 종단 파일로 되씀 | 끝 = `invalidateSectionDetail` → B07 로 넘어감 · 노선이 바뀐 [확정] = [저장]으로 · 다시 [확정] 알림 | — | `design_shared.ts:426-432` · `design_save.ts:224-228` · `B06_Section_Router_Confirm.py:368-496` |
| [초기화] | — | 상세 비움 · 새로고침(메모리 전부 버림) | `/route/reset` | DB 노선 행 지우고 초기값으로 · 파일 트리 복원 · 세션 초안 전부 지움(B08 · B09 뺌) | — | `design_reset.ts:30-62` · `B05_Profile_Router_Lifecycle.py:234-390` · `common_util_initial_snapshot.py:38-54 · 266-327` |
## [저장] 차례(서버 `B06_Section_Router_DesignSave.py:99-195` · 칸 = `design_save_api.ts` SAVE_PART_KEYS)
- [저장] · [확정] = 한 본문 한 요청(472-6) — 브라우저가 차례마다 조각(본문 · 저장 뒤 정리)을 모아 `POST /design/save`(`design_save.ts:183-237`) · 서버가 아래 차례로 안쪽 창구 함수를 부름 · 하나라도 실패 = DB 행 · storage 되돌림(`common_util_save_guard.py`) · 차례 이름째 알림 · 다 저장한 뒤에만 조각 정리 · 같은 프로젝트 저장 겹침 = 뒤 요청 기다림.
- 노선 바뀐 [저장](6-B) = 같은 본문에 `route`(노선 초안) · `carry`(옛 노선 세션 횡단 고침 원본 `routeCarryInputs` `B06_Section_Station_Keys.ts:314`) · 기본 조각만(화면 몫 건너뜀) · 늘 [저장](노선이 바뀐 [확정] = 노선만 저장 · 다시 [확정] 알림) · 되돌림 범위 + 배수 폴더 · 지표면 확정 · 다 저장하고 페이지를 다시 세운 뒤 옛 노선 번호 칸(노선 범위 초안 `clearRouteDrafts`) · `route-draft-hold` 비움(472-5).
| 차례 | 브라우저 조각 | 서버 | 노선 바뀐 [저장] | 위치(파일:줄) |
|---|---|---|---|---|
| route | `pendingRouteSave` → 본문 `route` | `replan_route`(`/route/replan` 체인 · 관 · 구조물 · 추가 측점 좌표 따라감 · 종횡단 · 계획고 이월) | 맨 앞 · 새 노선 id · 관 · 구조물 초안 다시 얹기 = Node `facility.rebase`(`rebasePipeDraft` · `rebaseStructureDraft`) · 구조물 판번호 = 저장 뒤 판 | `B06_Section_Router_DesignSave.py:128-145` · `B06_Section_Router_DesignSave_Route.py` |
| settings | `pageSettingsPart` | `save_page_settings` | 그대로 | `design_save.ts:137` |
| pipes | `pipesPart` + 화면 몫(배수판 다시 받음 · PUT 없음) | `put_pipe_points`(`confirm` = 배수유역 다시 셈) | 기본 조각만 · 다시 얹은 목록 | `design_save.ts:139` |
| structures | `structuresPart` | `write_structures` | 기본 조각만 · 다시 얹은 목록 | `design_save.ts:141` |
| plusStations | `plusStationsPart` | `put_plus_stations` | 기본 조각만 | `design_save.ts:143` |
| alignment | `alignmentPart` | `save_profile_alignment` | 노선 초안 칸(0) ?? 옛 노선 칸 → 서버가 새 노선 번호로 · 저장 뒤 두 칸 비움 | `design_save.ts:115-126` |
| culvertOptions | `culvertOptionsPart`(`culvertopt` · 노선 열쇠) | 저장 관 위에 얹어 `pipe_points.json` 다시 씀 | 옛 노선 열쇠(누가거리)로 읽음 | `B06_Section_Api_Culvert_Options.ts:102-106` |
| uphill | `routePartsBody` | `confirm_latest_route`(상단측 · 비정규 측점 횡단) | 안 보냄 · 서버도 안 받음 · 저장 뒤 세션 비우고 수 알림(요구 274) | `design_save.ts:149-155` |
| cross | `ctx.crossPart(confirm)` = 횡단 patch | `save_sections` · `confirm_sections` · 재계산 맨 앞 종단 경고 다시 씀 | 옛 노선 세션 칸 → 새 측점 patch = Node `stations.carry`(`routeCarryPatches` `B06_Section_Route_Carry_Patch.ts:368` · 넘김 규칙 `logic/B06_Section_Route_Carry.ts`) · 고침이 없어도 한 번 · 표준 횡단면 · 풍선 = `routeCarryPart` | `B06_Section_Router_DesignSave.py:172-195` · `B05_Profile_UI_RouteEdit_Apply.ts:157` |
- 저장 중 = `saving` 표시 · 묶음 맞춤은 저장 뒤로 미룸(`design_pipeline_page.ts` saving 분기).
+27
View File
@@ -0,0 +1,27 @@
## 개요
- 주소 둘(`#/b05-profile` 종단 · `#/b06-section` 횡단) = 페이지 하나 · 캐시를 같이 쓰고 [저장] 한 번에 함께 나감.
- 값의 길: 사용자 조작 → 화면 메모리 → 설계 가게 → 세션(sessionStorage) → 계산 묶음(노선 → 따라감 → 측점 → 지반 → 첫 계획선 → 종단 → 횡단 → 발자국) → [저장] · [확정] 때 서버가 정본을 다시 셈.
- 서버 층: 작업본(DB · 정본 파일) · 초기값(`initial_snapshot/`) · 파생 저장본(`_derived/` · 입력 지문 다를 때만 다시 셈).
- B05 · B06 을 고치기 전 이 페이지와 고칠 묶음 화면을 읽음 · 흐름을 바꾸면 같은 커밋에 손글(`docs/guide/B05_B06/`)도 고침.
## 지킬 규칙
- 파생값 = 입력(저장 정본 + 세션 초안)만의 함수 — 보기 차례 · 접힘 · 메모리 · 새로고침 · 들어온 길로 갈리지 않음.
- [저장] · [확정] = 서버가 입력만으로 정본을 다시 셈 · 브라우저 결과(면적 · 표본 · 측점)를 받아 적지 않음.
- 자동저장(디바운스 PUT) 없음 · 화면 잠금 = [저장] · [확정] · [초기화] · 추천 [적용]만.
- [초기화] = 초기값을 작업본에 덮어씀(다시 셈 아님).
- 상세 가게 메모리 = 페이지 수명 — 떠남 = [[forgetSectionMemory]] · [저장] · [확정] · [초기화] · 노선 저장 = [[invalidateSectionDetail]].
- 정본 자리 = 값을 낳은 주인 — 노선 시설 = `pipe_points.json` · `structures.json` · 횡단 값 = 횡단 패치.
- 측점 열쇠 = 한 벌([[common_util_station_key#stationKey]] · 파이썬 짝) · 노선 바뀜 = 관 · 구조물은 좌표(x · y)로 따라가고 누가거리는 다시 매김.
- 되돌리기 걸음 = 사용자 조작으로 값이 실제로 바뀐 때만 · 빈 칸(「""」「{}」「null」) = 칸 없음.
- 새 계산 = 셋 중 하나를 먼저 고름 — ① 파이썬 · TS 짝(거울 시험) · ② TS 를 서버 Node 로 · ③ 서버 단독.
- 새 세션 칸 = `A00_Common/b_page_state.ts` 등록부 한 줄 + 저장 타입.
## 사용자 결정
- 저장 밖 정본 쓰기 셋 — 넓힘 · [측점 만들기] · 추천 [적용](즉시 저장 · 확인창 · 요구 275 ②).
- 추천 [적용] = 새 노선 기준 초기화 — 옛 계획고 · 편집 · 횡단 손 입력 안 넘김(요구 277).
- 노선이 바뀌면 자리 칸(암 경계 · 2단 경사 · 손 상단측)은 같은 자리 측점만 넘김 · 새 자리 = 기본값(요구 275).
- 단면 모양 기본값 짝 다름(브라우저 「left_cut」 · 서버 기본 표) — 사용자 협의 전 손대지 않음.
- 속도 목표는 기능 완성 뒤 따로 봄.
-63
View File
@@ -1,63 +0,0 @@
---
type: concept
status: stable
related_pages: ["[[design_page/index]]", "[[design_page/operations_common]]", "[[design_page/values]]", "[[design_page/calculations]]", "[[design_data_lifecycle]]"]
last_updated: 2026-10-19
source: ["CLAUDE.md §5", "docs/raw/조사/2026-10-19_465_요구272_값안변함.md", "docs/raw/조사/2026-10-19_470_B05B06_데이터흐름.md", "PLAN.md 466 · 467 · 468 · 472"]
---
# B05 · B06 지킬 규칙
## 불변 규칙
| 규칙 | 지키는 자리(파일:줄) | 시험 |
|---|---|---|
| 파생값 = 입력(저장 정본 + 세션 초안)만의 함수 — 보기 차례 · 접힘 · 메모리 · 새로고침 · 들어온 길로 안 갈림 | `logic/design_pipeline.ts:49` STAGE_DEPS · `design_pipeline_page.ts:545-568` reconcile | `test_272_*` |
| 상세 가게 메모리 = 페이지 수명 — 떠남 = `forgetSectionMemory` · [저장] · [확정] · [초기화] · 노선 저장 = `invalidateSectionDetail` | `design_page.ts:294` · `design_shared.ts:428 · 435` · `design_reset.ts:41` · `design_view_profile.ts:469` · `B05_Profile_UI_RouteEdit_Apply.ts:171 · 211` | `test_272_detail_memory` |
| [폐기] = 세션 고침 칸만 지움 · 메모리는 떠남(dispose)이 비움 | `design_leave.ts:85-87` · `design_page.ts:286-295` | `test_346_4_design_leave` |
| 조용한 차례(열 때 · 노선 초안 버림) 끝 = 가게 측점 · 종단 = 지금 상세 측점 · 저장 기준 · 같은 목록은 다시 안 냄 | `design_pipeline_page_publish.ts` shown · opened · `design_pipeline_page.ts:392-393 · 530` | `test_272_drop_store_stations` |
| 노선 초안 없는 차례 = 서버 정본 측점 기하 · 표본을 브라우저 값으로 안 덮음(같은 자리 0.01 m · 축 1e-3 → 앞 횡단 → 그 자리 옛 횡단 → 저장 정본) · 지금 상세에 없던 측점이 정본 자리로 돌아옴(관 지움 ↶) = 정본 설계 | `logic/B06_Section_Station_Swap.ts:171-182 · 241-249 · 304-315` · `B06_Section_Section_Store.ts:241-248` | `test_272_swap_keeps_canon` |
| 노선 초안 차례 = 새 · 옮긴 측점만 브라우저 지반 표본 | `logic/B06_Section_Station_Swap.ts:249-251` | `test_346_2_station_swap` |
| 다시 셈 입력 물러섬 = 세션 고름 → 저장 정본 설계 → config 기본값 · 고름 단추는 메모리 상세 설계를 안 덮음 | `B06_Section_Cross_Refresh.ts:402-408` · `B06_Section_UI_Page_Design_Sync.ts:59-104` | `test_272_choice_undo` |
| 소단 사본 쓰는 자리 = 시설 줄 하나(B05 만 열어도) · 같으면 안 씀 · 사본 없으면 저장분 `design.berm` | `design_facility_lane.ts:151-166` · `B06_Section_Cross_Refresh.ts:362-368` | `test_272_berm_source` |
| 두 보기가 보는 파생값은 페이지 한 벌이 숨은 동안도 셈 · 보기는 그리기만 · 접힘 = 그리기만 건너뜀 | `design_facility_lane.ts:1-12` · `B05_Profile_UI_Profile_Render.ts:211-226` | `test_272_hidden_summary` · `test_271_facility_lane` |
| 되돌리기 걸음 = 사용자 조작으로 값이 실제로 바뀐 때만 · 빈 칸(「""」「{}」「null」) = 칸 없음 · 빈 칸 쓰기 = 칸 지움 | `B05_Profile_UI_Profile_History.ts:50 · 235-236` · `B06_Section_Session_Cells.ts` | `test_272_history_empty_cells` |
| 시설 줄 측점 = 종류 표 · 구조물 · 관 알림이 다 온 뒤만 심음 · 그 전 종단도 = 저장 측점 · 저장 기준 그대로(모름 ≠ 빈 목록) | `design_facility_lane.ts` known · `design_profile.ts` planted · `design_profile_base.ts` listedBase · `design_view_profile_sections.ts` stationsReady · `B05_Profile_UI_Profile_Render.ts` deriveProfile | `test_473_lane_ready` |
| 짝 없는 새 측점(구조물 · 추가 측점 · 새 관) = 계획선 꼭짓점 안 세움 · 짝 있는 옮긴 측점 = 꼭짓점 따라감 | `common_util_structure_station.ts:356` · `B05_Profile_Router_Confirm.py:306-400` | `test_427_1_follow_seat` · `test_459_route_seat_mirror` |
| 구조물 측점 다시 뜸([저장] 상단측 차례 · 측점 맞춤) = 같은 자리 옛 줄의 상단측 출처(`user` 면 값도) · 포장 제안 표기 들고 감 · 새 자리 포장 표기 = 지금 계획선 | `B05_Profile_Router_Confirm.py:426-465` | `test_472_station_user_fields` |
| [저장] · [확정] = 서버가 입력만으로 정본 다시 셈 · 브라우저 결과(면적 · 표본 · 측점) 받아 적지 않음 | `design_save.ts:58-68` · `B06_Section_Router_Confirm.py:203-215` | `test_b06_save_recompute` |
| 자동저장(디바운스 PUT) 없음 · 화면 잠금 = [저장] · [확정] · [초기화] · 추천 [적용]만 | `design_save.ts:249` | — |
| 측점 열쇠 = `common_util_station_key` 한 벌 · 노선 바뀜 = 관 · 구조물 좌표(x · y) 따라감 · 누가거리 다시 매김 | `common_util_station_key.ts:32` · `common_util_route_follow.ts:291` | `test_344_3_station_key_mirror` · `test_410_follow_xy` |
| 노선 바뀜 넘김 = 열쇠마다 지반 · 측구 종류 · 측구 선택 · 사면 쉬프트 · 소단 · 소단 깊이 · 자리 칸(`SEAT_KEYS` = 암 경계 · 암 절토 경사 · 2단 경사 · 손 상단측 = 단면 유형 · 측구 방향)은 같은 자리(`sameSeat` · 중심 0.01 m · 축 1e-3) 측점만 · 새 자리 = 기본값 · 세션 값은 쓴 때 자리(`@seat`)로 판정(↶ ↷ 유지) · 끌기 갈아끼움 · 미리보기 · Node 넘김 · 서버 노선 저장 한 규칙 | `logic/B06_Section_Station_Swap.ts` SEAT_KEYS · unseated · `logic/B06_Section_Route_Carry.ts` carryKeyedStates · `B05_Profile/logic/B05_Profile_Pipeline_Detail.ts` crossAtSeat · `B05_Profile_Route_Save_Parts.py` carry_cross_inputs | `test_475_3_seat_carry` |
| 노선 초안 ↶(버림) = 차례 하나 · 버림 동안 종단도 그리기 멈춤 · 낡음 다시 셈 미룸 → 되돌림 · 저장 기준 맞춤 · 뒤 차례 끝에 한 번 그림(사이 섞인 유토곡선 없음) | `design_pipeline_page.ts:642 · 403` · `B06_Section_Section_Store.ts:316` | `test_472_5c_drop_once` |
| 추천 [적용] = 새 노선 기준 초기화 — 종단 = 새 규칙 초기 종단(끔 · 균형 셈 실패 = 1차 계획선 438 · L 20 · 켬 = 균형 종단을 꼭짓점에 얹음) · 옛 계획고 · 편집 · L · 횡단 손 입력 안 넘김(요구 277 · 측점마다 기본 설계) · 손 노선 저장만 옮김 · 넘김 | `B05_Profile_Route_Save.py` run_route_save_chain `initial` · carries_cross_inputs · 미리 보기 = `B05_Profile_Engine_InitialProfile`(같은 조각) | `test_476_1_apply_initial` · `test_476_1_recommend_initial` |
| B05 · B06 캐시 = 한 벌 · [저장] 한 번에 함께 나감 | `design_shared.ts:395-448` · `design_save.ts:237` | — |
| 관 · 구조물 주인 = 가게 칸 하나 — 화면 사본은 가게 알림으로 맞춤(제 쓰기는 같은 값 · 남 고침은 다시 실음) | `design_view_profile.ts` 가게 구독 · `design_view_cross.ts` onShop · `B06_Section_UI_Page_Structures_Panel.ts` seen | `test_472_5_shop_owner` |
| 정본 자리 = 값을 낳은 주인 — 노선 시설 = `pipe_points.json` · `structures.json` · 횡단 값 = 횡단 패치 | `B05_Profile_Api_Pipes_Draft.ts:30-37` · `B05_Profile_Structures_Router.py:386-405` · `B06_Section_Section_Store.ts:449` | — |
| 열기만 = 초안 칸 없음 — 값이 저장분과 같으면 칸 없음(표준 횡단 = 결과 칸 `section-context` 에서 읽음) · 카드 고름 = 바꾼 칸만 · 소단 사본 = 떠남 확인 밖 | `B06_Section_UI_Standard_Panel.ts` effectiveStandardCross · persist · `B06_Section_UI_Cross_Design.ts` emit · `B06_Section_UI_Page_Design_Sync.ts` handleDesignChange · `design_leave.ts` NOT_EDITS | `test_472_open_no_draft` |
| 관 자리 읽기 한 벌 = 지금 노선에 얹은 관 지점(측점 계획 · 관 지점 GET · 서버 상세 형상 얹기 · 카드 이름 · 세월교 노면 하강 · 물넘이포장 구간 · B07 관 글 · 화면) — 지문 어긋난 옛 저장분도 같은 자리 · 관 파일 누가거리를 그대로 읽지 않음 | `B05_Profile_Engine_Sections.py:249` route_pipe_points | `test_472_d_pipe_route_attach` · `test_472_d_route_pipe_readers` |
## 바꿀 때 할 일
| 경우 | 할 일 |
|---|---|
| 새 계산 | CLAUDE.md §5 ① 짝 · ② TS → 서버 Node · ③ 서버 단독 중 먼저 고름 · [[design_page/calculations]] 한 줄 |
| ① 짝 | 두 파일 머리에 짝 · 거울 시험 이름 · `resources/tester` 거울 시험 |
| ② TS → Node | 끌려가는 파일 모듈 맨 위 DOM · window · sessionStorage 금지 · 묶음 다시 지음 |
| 조작 · 흐름 바꿈 | 같은 커밋에 [[design_page/operations_b05]] · [[design_page/operations_b06]] · [[design_page/operations_common]] · [[design_page/values]] 고침 |
| 새 세션 칸 | `b_page_state.ts` 등록부 · 저장 타입 · [[design_page/session_keys]] |
| 새 메모리 사본 | 비우는 때(떠남 · [폐기] · [저장] · [확정] · [초기화])를 정하고 [[design_page/values]] 사본 칸에 적음 |
| 줄 번호 | 고친 함수의 줄이 바뀌면 이 폴더 줄 번호도 고침 |
## 예외 · 남은 틈
| 내용 | 자리(파일:줄) | 상태 |
|---|---|---|
| 저장 밖 정본 쓰기 셋 — 넓힘 `/regenerate` · [측점 만들기] · 추천 [적용](즉시 저장 · [저장]과 같은 프로젝트 잠금 · 떠 두고 실패면 되돌림 · 476 ④) | `B06_Section_Router.py:378-388` · `B06_Section_Router_Stations.py:236-296` · `B05_Profile_UI_RouteEdit_Apply.ts:220-245` · `B05_Profile_Router_Replan.py` replan_route | 사용자 확정(요구 275 ②) · 확인창 |
| `pipe_points.json` 한 [저장]에 두 번 PUT(관 · 관 옵션) | `B06_Section_Api_Culvert_Options.ts:67-95` | 코드 확인 |
| 노선 바뀐 [저장]의 관 옵션 차례 = 옛 노선 열쇠(누가거리 · 관이 따라 옮긴 새 자리와 안 맞으면 안 얹힘) | `design_save.ts:145` · `B06_Section_Router_DesignSave.py` `_patched_points` | 추정 · 옛 길과 같음 |
| ↶ ↷ 가 세션을 직접 되씀 — 관 · 구조물 · 추가 측점은 되쓴 뒤 가게 듣는 쪽을 부름(`announceDesignStore` · 472-5) · B06 조정창 = 세션 칸이 주인(사본 없음) · 칸이 바뀐 걸음만 상세 얹기 · 카드 · 면적 다시(472-5 5-D) · 암선 · 암 경사 · 상단측 = 세션 칸이 주인(사본 없음 · 472-5) | `B05_Profile_UI_Profile_History.ts` apply · `design_store.ts` announceDesignStore · `B06_Section_UI_Page_Station_Controls.ts` onRestore · `B06_Section_UI_Page_Persist.ts` readCells | 관 · 구조물 = `test_472_5_shop_owner` · 관 초안 없는 걸음 = 저장분 · ↶ 켜짐 = `test_472_d_b05_pipe_undo` · 조정창 = `test_472_5d_adjust_owner` · 암선 · 암 경사 = `test_472_5r_rock_owner` |
| 스냅 켬 · 간격 = 되돌리기 밖 | `B05_Profile_UI_RouteEdit_SnapStep.ts:74-78` | 추정 |
| `flushDesignStore` 부름 없음 — 멈춘 뒤 1 s 안 새로고침 = 가게 세션 사본 잃음(다시 셈) | `design_store.ts:179` | 코드 확인 |
| 「[]」 — 되돌리기 = 값 · 떠남 확인 = 빈 칸 | `B05_Profile_UI_Profile_History.ts:50` · `design_leave.ts:41-42` | 코드 확인 |
| 단면 모양 기본값 짝 다름(X1) | `B06_Section_Cross_Refresh.ts:438` · `B06_Section_Router_Design.py:402` | 사용자 협의 |
-50
View File
@@ -1,50 +0,0 @@
---
type: concept
status: stable
related_pages: ["[[design_page/index]]", "[[design_page/values]]", "[[design_page/operations_common]]"]
last_updated: 2026-10-19
source: ["docs/raw/조사/2026-10-19_470_B05B06_데이터흐름.md", "A00_Common/b_page_state.ts"]
---
# 세션 열쇠 등록부 — B05 · B06
- 등록부 = `A00_Common/b_page_state.ts` `STATE_REGISTRY` · 열쇠 꼴 `aislo:{통}:{이름}:{프로젝트}[:{노선}]` · 없는 이름은 저장 안 됨(콘솔 알림).
- 통 — draft = 사용자 값([폐기] · [저장] 대상) · result = 계산 · 서버 사본 · pref = localStorage(화면 자리).
- 새 칸 = 등록부 한 줄 + 저장 타입 + 이 표 한 줄 · [폐기] 안 지울 칸은 `NOT_EDITS`(`design_leave.ts:27-34`).
## draft
| 열쇠 | 위치(`b_page_state.ts`:줄) | 쓰는 쪽 | [저장] 차례 |
|---|---|---|---|
| `route-draft` · `route-draft-hold` | `:218` · `:220` | 노선 편집기 다리 · 초안 전 상세 | route · hold 는 안 감 · 노선 저장 뒤 hold 비움 |
| `alignment-draft` | `:213` | 종단 편집(노선 · 초안 노선 0) | alignment |
| `pagesettings` | `:215` | 페이지 설정 폼 | settings |
| `pipes` · `structures` · `plus-stations` | `:136` · `:127` · `:129` | 시설 줄 · 배수판 · B06 패널 · 추가 측점 창 | pipes · structures · plusStations |
| `culvertopt` · `kindreset` | `:154` · `:152` | 관 조정창 옵션 예약 · 종류 바꾼 관 | culvertOptions · cross(structure_reset) |
| `uphill` | `:131` | 상단측 | uphill(노선 바뀌면 cross 뒤) |
| `crossdesign` · `shoulderext` · `bermdepth` | `:196` · `:198` · `:200` | 카드 단추(바꾼 칸만 · 앞서 바꾼 칸에 얹음) · 설계선 쉬프트 · 소단 깊이 | cross |
| `rockb` · `cutslope` · `crossw` | `:158` · `:162` · `:184` | 암선 · 암 경사 · 측점 반폭 | cross |
| `revetx` · `revetlink` · `basinadjust` · `inletstruct` · `extrawall` · `extraspan` · `fordadjust` · `boxadjust` | `:185-192` | 조정창 칸(값 주인 · 창구 = b_page_state 열쇠 `B06_Section_Session_Cells` · 메모리 사본 없음 · 빈 칸 = 칸 지움) | cross |
| `structure-notes` · `structure-note-cads` · `detail-table` | `:121` · `:123` · `:125` | [상세] 모달 | cross |
| `std-cross` | `:175` | 표준횡단 패널 — 저장분(`section-context` 의 `stored_standard_cross_section`) · 없으면 config 와 다를 때만 · 같으면 칸 없음([폐기] · [저장]에도 안 지움 · [초기화]만) | cross(`standard_cross_section`) |
| `berm` | `:166` | 시설 줄 `syncBerm` 만(구조물 파생 · 열 때 씀 · 떠남 확인 밖) | 안 감 — 서버가 `structures.json` 에서 |
| `cross-display` | `:177` | 전역 표시 반폭 | 안 감 |
| `selection` · `structure-pick` · `pipe-ids` · `pipe-option-queue` · `structure-type-cache` | `:148` · `:138` · `:144` · `:146` · `:150` | 고름 · 관 id 장부 · 옵션 넘김 · 종류 캐시 | 안 감 · `structure-type-cache` 는 저장 뒤 비움 |
## result
| 열쇠 | 위치(`b_page_state.ts`:줄) | 쓰는 쪽 | 비우는 때 |
|---|---|---|---|
| `latest` | `:224` | 노선 최신 응답 | 노선 저장 · 추천 [적용] 뒤 |
| `section-context` · `std-cross-default` · `rock-boundary-default` | `:230` · `:234` · `:240` | 횡단 맥락 · 서버 기본값 | 새로 받을 때 |
| `section-detail` | `:232` | 상세 서버 정본 사본(브라우저 셈 안 실음) | `invalidateSectionDetail` |
| `routeedit-initial` | `:247` | 추천 노선 | 추천 다시 셈 |
| `design-stations` · `design-profile` · `design-footprint` | `:249-251` | 가게 메모리 칸의 세션 사본(멈춘 뒤 1 s) | 가게 칸 지움 |
## pref(localStorage)
| 열쇠 | 위치(`b_page_state.ts`:줄) | 쓰는 쪽 |
|---|---|---|
| `profile-collapsed` · `profile-height` · `plan-peek-height` | `:48` · `:53` · `:55` | 종단 판 접힘 · 높이 · B06 평면 패널 높이 |
| `routeedit-view` · `routeedit-locks` · `routeedit-terrain` · `routeedit-snap-step` | `:72-78` | 평면 보기 · 자물쇠 · 지형 · 스냅 간격 |
| `masshaul-*` · `table-*` · `section-panel-*` · `cross-columns` · `mass-haul-badge-pos` | `:79-113` | 유토 판 · 표 · 횡단 판 자리 |
-34
View File
@@ -1,34 +0,0 @@
---
type: concept
status: stable
related_pages: ["[[design_page/index]]", "[[design_page/calculations]]", "[[design_page/session_keys]]", "[[design_page/rules]]"]
last_updated: 2026-10-19
source: ["docs/raw/조사/2026-10-19_470_B05B06_데이터흐름.md", "docs/raw/조사/2026-10-19_472_구조다시세움_목록.md", "PLAN.md 466 · 467 · 468"]
---
# 값별 주인 · 사본 · 셈 자리
- 주인 = 서버 작업본(정본) 자리 · 브라우저 쪽 창구 = 설계 가게 칸(있으면) · 나머지는 사본.
- 셈 자리 — TS = 브라우저 · 파이썬 = 서버 [저장] 정본 · Node = 같은 TS 를 서버 묶음으로 · 짝 = TS ↔ 파이썬 두 벌(거울 시험 필수).
| 값 | 주인(정본) | 사본 | 셈 자리 · 짝 · 거울 시험 | 맞추는 때 |
|---|---|---|---|---|
| 평면 선형(교점 · R · L · 스냅) | 노선 파일 · 곡선표 · DB `routes` · `route_points` | 편집기 메모리(`B05_Profile_UI_RouteEdit.ts:124-137`) · 사진첩 · pref `routeedit-locks` · `routeedit-snap-step` · 세션 `route-draft` · `route-draft-hold` · 가게 `routeDraft` · 묶음 입력 · `latest` | TS `logic/B05_Profile_UI_RouteEdit_Curve.ts:243` ↔ 파이썬 `common_util_route_polyline.py:579` · `test_route_polyline_browser_mirror` | 놓을 때 세션 · [저장]은 편집기 메모리를 먼저 읽음 · 저장 뒤 페이지 다시 세움 |
| 측점 목록 · 열쇠 | DB `cross_sections` 행 · 종단 파일 stations · 서버 `generate_sections` | 묶음 stations · 가게 `stations`(메모리) → 세션 `design-stations` · 상세 cache · 세션 `section-detail` · canon · `design_pipeline_page.ts` savedStations(sub2 관 · 구조물 줄로 셈 · 남김) · `B06_Section_Station_Keys.ts:92` 목록 | TS `logic/design_pipeline_stations.ts` · `logic/B05_Profile_Sections_Stations.ts:276` ↔ 파이썬 `B05_Profile_Engine_Sections_Core.py:449` · 열쇠 `common_util_station_key.ts:32` ↔ `.py:32` · `test_344_3_pipeline_stations_mirror` · `test_344_3_station_key_mirror` | 가게 `stations` 알림 → 갈아끼움(구독 둘 · 같은 목록이면 안 함) · 조용한 차례 끝 = 상세 측점(`design_pipeline_page_publish.ts` shown) · [저장] 뒤 비움 |
| 측점 자리 · 축 | 서버 측점 틀(저장 경로 폴리라인) | 브라우저 측점 틀(`/route/plan` planned 선) · 상세 cache frame | TS `logic/B05_Profile_UI_RouteEdit_Frame.ts:61` ↔ 파이썬 `B05_Profile_Engine_Sections_Core.py:449-519`(같은 식 · 입력 선 다름 · mm 차) · `test_b05_routeedit_frame_mirror` | 노선 초안 없는 차례 = 정본 자리 · 축 유지(`logic/B06_Section_Station_Swap.ts:238-247`) |
| 지반 표본 | 서버 `sample_xy`(npz · 저장 노선 선) → `cross_*.json` samples | 지형 격자(`B05_Profile_Api_TerrainGrid.ts:29-31` · IndexedDB) · 묶음 ground · 상세 cache samples | TS `common_util_surface_sampler.ts:65` ↔ 파이썬 `common_util_surface_sampler.py:210` · `test_344_1_surface_sampler_mirror` | 갈아끼움 — 노선 초안 없음 = 같은 자리(0.01 m · 축 1e-3) 앞 횡단 → 그 자리 옛 횡단 → 저장 정본 표본 · 노선 초안 = 새 · 옮긴 측점만 브라우저 표본 |
| 계획고 편집(▲▼ · 직선화 · 자유 · 쉬프트 · 시종점) | 종단 파일 `profile_alignment.edits` | ★ 가게 `alignmentDraft`(세션 `alignment-draft`) · 가게 `alignmentSaved`(서버 저장분 · 메모리 · `B05_Profile_UI_Profile_Edit.ts:127 · 235`)(노선 · 초안 노선 0) · 묶음 입력 · 가게 `profile` → `design-profile` · 상세 `longitudinal` | TS `logic/B05_Profile_UI_Profile_Alignment.ts:401` ↔ 파이썬 `B05_Profile_Engine_Grade_Alignment.py:494` · `test_426_profile_straight` · `test_432_1_curve_law` | 커밋마다 세션 · [저장] = 서버 다시 셈 |
| 종단 R | 같은 `edits.curve_radii` | 위와 같음 · 칸은 L · 저장은 R | TS `logic/B05_Profile_Profile_CurveLaw.ts` ↔ `B05_Profile_Engine_Grade_CurveLaw.py` · `test_432_1_curve_law` | 위와 같음 · R = L/Δ 로 적어 Δ 바뀌면 L 흐름 |
| 계획선 꼭짓점(시설 측점) | 종단 파일 base PVI | 묶음 planline · 종단도 | TS `common_util_structure_station.ts:356` followMovedNodes ↔ 파이썬 `B05_Profile_Router_Confirm.py:306` `_follow_moved_stations` · `:220` `_seat_born_nodes` · `test_427_1_follow_seat` · `test_459_route_seat_mirror` | 짝 있는 옮긴 측점 = 꼭짓점 따라감 · 짝 없는 새 측점 = 안 세움 |
| 횡단 입력(카드 · 조정) | DB `cross_sections.design` 패치 칸 | ★ 세션 칸(`crossdesign` · 조정창 칸 등 · 조정 칸 창구 = b_page_state 열쇠 `B06_Section_Session_Cells` · 메모리 사본 없음 · 472-5 5-D) · cache 설계 칸(조작 즉시 얹음 · 얹기 = 고치기 전 칸 + 지금 세션 칸) | 입력 도구(셈 없음) | 조작마다 세션 · [저장] 횡단 패치 |
| 횡단 설계 결과(설계선) | DB `cross_sections.design` | ★ 상세 가게 — cache(살아 있는 객체 · 제자리 고침) · 칸 셋(`B06_Section_Section_Canon.ts:93-143` 저장 정본 상세 · 초안 전 hold 상세 · 노선 초안 상세 = `pipelineDetail` · 472-5) · 세션 `section-detail`(서버 사본만) · canon 설계 사본 · 묶음 cross — 계산 묶음 페이지는 지문(노선 · 표본)만 | TS `common_util_cross_design.ts:267` ↔ 파이썬 `B06_Section_Engine_Design.py:195` · `test_b06_cross_design_mirror` | 다시 셈마다 cache 제자리 · 입력 물러섬 = 저장 정본(`B06_Section_Cross_Refresh.ts:402-408`) · 페이지 수명 · [저장]·[확정]·[초기화] 뒤 비움 |
| 측점 면적 | DB 면적 — 표준 측점 = 파이썬 · 구조물 측점 = Node | cache 면적 · 묶음 cross | TS `common_util_cross_design_areas.ts` ↔ 파이썬 `B06_Section_Engine_Areas.py` · 구조물 = `B06_Section_Structure_Layouts.ts`(Node 한 벌) | [저장] 때 서버가 브라우저 면적 버리고 다시 셈 |
| 누가토량 | 종단 파일 `mass_haul` | 그릴 때 셈 · 배지 · `_derived/haul` · `B05_Profile_Api_HaulPlan.ts:28` kept | TS `common_util_mass_haul.ts:426` 한 벌(브라우저 · Node) | 그릴 때(접혀도 셈) · [저장] |
| 구조물 | `structures.json` | `B05_Profile_UI_Page_Structures.ts:78` · `B06_Section_UI_Page_Structures_Panel.ts:175` · 시설 줄 `design_facility_lane.ts:97` · savedStructures · `design_page_data.ts:25` · 세션 `structures` · `structure-type-cache` · cache | 따라감 TS `common_util_route_follow.ts:291` ↔ 파이썬 `common_util_route_follow.py:120` · `test_410_follow_xy` | 주인 = 가게 `structures`(세션) · 사본은 화면 사본 — 가게가 바뀌면(되돌리기 포함 · `announceDesignStore`) 종단 목록 `takeStructures`(보일 때) · 횡단 판 `foreign()` → 다시 실음 · 숨은 보기는 보일 때 load |
| 관 | `pipe_points.json` | 배수판 둘(`B05_Profile_UI_Drainage_Panel.ts:123 · 142`) · `B06_Section_UI_Page_Structures_Panel.ts:176` · 시설 줄 `:101` · `design_view_profile.ts:116` · savedPipes · `design_pipe_ids.ts:24` · 세션 `pipes` · `pipe-ids` · `culvertopt` · `kindreset` · `pipe-option-queue` · cache culvert · 유역 pkl | 관 길이 TS `design_structure_pipe_calc.ts` · `B06_Section_Structure_Layouts.ts:95`(Node 한 벌) · `test_b06_pipe_length` | 주인 = 가게 `pipes` · 배수 편집기는 가게가 바뀌면 숨어도 `adoptPipes`(제 쓰기 메아리 빼고) · 횡단 판 `foreign()` → 다시 실음 · 고름 · 알약 관 = 시설 줄(`shownPipes`) · 배수 다시 나눔 응답 · [저장] 뒤 다시 받음 |
| 추가 측점 | `plus_stations.json` | `B05_Profile_Api_PlusStations.ts:14 · 74 · 76` · 세션 `plus-stations` | 따라감 TS `followPlusDraft` ↔ 파이썬 `B05_Profile_Plus_Stations.py:82` · `test_459_route_seat_mirror` | 노선 초안 follow 뒤 · [저장] |
| 소단 구간 · 깊이 | 구간 = `structures.json` · 깊이 = DB 패치 | 세션 `berm`(시설 줄만 씀) · `bermdepth` · cache `design.berm` · 초안 노선 `bermAt`(`design_shared.ts:248`) | TS `B06_Section_UI_Page_Persist.ts:157` bermSpansFromStructures ↔ 파이썬 `B06_Section_Engine_Structures_Wall.py:124` load_berm_spans · `test_272_berm_source` | 구조물 바뀜 → 시설 줄 `syncBerm`(같으면 안 씀) → cross 한 번 · 사본 없으면 저장분 |
| 페이지 설정 | DB 단계 2 설정 · 종단 정책 | ★ 가게 `pageSettings`(세션 `pagesettings`) — 폼 · 종단도 기울기 한도 · 시작 측점이 듣고 최소고 강제는 바로 읽음 | 정책 파이썬 기준표 ↔ TS `B05_Profile_UI_Panel_Fields.ts` · `test_442_2_grade_limit_mirror` | 열 때 두 보기가 얹음 · [저장] 설정 차례 |
| 표준 횡단 | 종단 행 `standard_cross_section` | 세션 `std-cross` · `std-cross-default` · `section-context` | 서버 설계선 다시 | [전체 반영] · [저장] |
| 3D | corridor 파일(Node) | `B05_Profile_UI_Corridor.ts:45` cache | Node `B05_Profile_Corridor_Prebuild.py` | 노선 초안 동안 낡음 표시 · [저장] 뒤 |
| 되돌리기 사진첩 | 없음(메모리 · 탭 수명) | 사진 n 장 = 세션 draft 칸 글 | — | 조작 뒤 `record()` · 같은 사진이면 걸음 아님 |
+52
View File
@@ -0,0 +1,52 @@
# 프로그램 문서 사이트(PLAN 478) — 원고 = docs/tools/build.py 가 _build/src 에 만듦
site_name: Aislo 프로그램 문서
docs_dir: _build/src
site_dir: _build/site
use_directory_urls: false
theme:
name: material
language: ko
features:
- navigation.sections
- navigation.indexes
- toc.integrate
- search.highlight
plugins:
- search:
separator: '[\s\-_.]+'
- autorefs
- mkdocstrings:
handlers:
python:
paths: [..]
options:
show_source: false
show_root_heading: true
heading_level: 2
members_order: source
filters: []
docstring_style: null
show_signature_annotations: true
separate_signature: true
hooks:
- tools/nav_hook.py
markdown_extensions:
- attr_list
- md_in_html
- admonition
- tables
- toc:
permalink: true
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
validation:
links:
anchors: warn
+6
View File
@@ -0,0 +1,6 @@
# 문서 사이트 도구 (PLAN 478)
- 설치 — `npm --prefix docs/tools install` · `venv/Scripts/python.exe -m pip install -r docs/tools/requirements.txt`
- 원고 — `venv/Scripts/python.exe docs/tools/build.py` (추출 `docs/_data` → `docs/_build/src` · `--site` = 사이트까지 · `--stamp` = 손글 지문 고침)
- 띄우기 — `cd docs && ../venv/Scripts/python.exe -m mkdocs serve -a 127.0.0.1:8090`
- 묶음 규칙 — `groups.yml` · 손글 — `docs/guide/<페이지>/page.md` · `groups/<묶음>.md`
+457
View File
@@ -0,0 +1,457 @@
"""문서 사이트 원고 만들기(PLAN 478-2).
추출(docs/_data/*.json) + 손글(docs/guide/<페이지>/page.md · groups/<묶음>.md) → docs/_build/src
index.md 처음
<페이지>/index.md 개요 · 묶음 상자 · 묶음 사이 흐름 그림 · 손글(규칙 · 사용자 결정)
<페이지>/<묶음>.md 손글 설명 · 묶음 안 흐름 그림 · 세션 칸 · 서버 주소 · 함수 칸
<페이지>/none.md 묶음 없음
ts/ · py/ 함수 화면 — TypeDoc(markdown) · mkdocstrings(griffe)가 만듦
.nav.json 왼쪽 메뉴(nav_hook.py 가 읽음)
함수 목록 지문이 손글 지문과 다른 묶음 = 「설명 확인 필요」 · 맞으면 --stamp.
venv python docs/tools/build.py [--data D] [--out O] [--site] [--stamp]
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import shutil
import subprocess
import sys
import time
from collections import Counter
from pathlib import Path
import griffe
import yaml
sys.path.insert(0, str(Path(__file__).resolve().parent))
import group as grouping # noqa: E402
TOOLS = grouping.TOOLS
DOCS = TOOLS.parent
ROOT = DOCS.parent
REF = re.compile(r"\[\[([^\]]+)\]\]")
MAX_GRAPH = 40
# ── 읽기 ────────────────────────────────────────────────────────────────────
def load_data(data: Path) -> dict:
out = {}
for name in ("functions", "session_keys", "api", "registry"):
path = data / f"{name}.json"
out[name] = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
if not out["functions"]:
raise SystemExit(f"추출 없음: {data / 'functions.json'}")
return out
def read_hand(path: Path) -> tuple[dict, str]:
if not path.exists():
return {}, ""
text = path.read_text(encoding="utf-8")
if text.startswith("---\n"):
head, _, body = text[4:].partition("\n---\n")
return yaml.safe_load(head) or {}, body.strip()
return {}, text.strip()
def stamp(path: Path, print_: str) -> None:
meta, body = read_hand(path)
meta["지문"] = print_
head = yaml.safe_dump(meta, allow_unicode=True, sort_keys=False).strip()
path.write_text(f"---\n{head}\n---\n\n{body}\n", encoding="utf-8")
# ── 함수 화면(기성 도구) ───────────────────────────────────────────────────
def run_typedoc(ts_files: list[str], out: Path) -> dict[tuple[str, str], str]:
"""TypeDoc 으로 ts/ 함수 화면 · (파일, 이름) → 원고 안 경로."""
if not ts_files:
return {}
opts = {
"entryPoints": [(ROOT / f).as_posix() for f in ts_files],
"tsconfig": (ROOT / "tsconfig.json").as_posix(),
"skipErrorChecking": True,
"plugin": ["typedoc-plugin-markdown"],
"out": (out / "src" / "ts").as_posix(),
"json": (out / "typedoc.json").as_posix(),
"readme": "none",
"entryFileName": "index.md",
"logLevel": "Error",
}
opt_file = out / "typedoc.options.json"
opt_file.write_text(json.dumps(opts, ensure_ascii=False), encoding="utf-8")
subprocess.run(
["node", str(TOOLS / "node_modules/typedoc/bin/typedoc"), "--options", str(opt_file)],
cwd=ROOT,
check=True,
)
return typedoc_pages(out)
def typedoc_pages(out: Path) -> dict[tuple[str, str], str]:
project = json.loads((out / "typedoc.json").read_text(encoding="utf-8"))
modules = [m for m in project.get("children", []) if m["kind"] == 2] or [
{**project, "name": ""}
]
pages = {}
for mod in modules:
base = f"ts/{mod['name']}/" if mod["name"] else "ts/"
for c in mod.get("children", []):
folder = {64: "functions", 32: "variables"}.get(c["kind"])
if folder and c.get("sources"):
rel = f"{base}{folder}/{c['name']}.md"
if (out / "src" / rel).exists():
pages[(c["sources"][0]["fileName"], c["name"])] = rel
return pages
def py_ident(file: str) -> str:
return file.removesuffix(".py").replace("/", ".")
def griffe_names(file: str) -> set[str]:
"""griffe 가 보는 함수 이름(모듈 함수 · 클래스 메서드 `Class.name`)."""
mod = griffe.load(py_ident(file), search_paths=[str(ROOT)], allow_inspection=False)
names: set[str] = set()
def walk(obj, prefix: str) -> None:
for name, m in obj.members.items():
if m.is_alias:
continue
if m.is_function:
names.add(prefix + name)
elif m.is_class:
walk(m, f"{prefix}{name}.")
walk(mod, "")
return names
def write_py_pages(py_files: list[str], src: Path) -> dict[tuple[str, str], str]:
"""mkdocstrings 원고 · (파일, 이름) → autorefs 식별자."""
refs = {}
lines = ["# 파이썬 함수 화면", ""]
for file in py_files:
ident = py_ident(file)
(src / "py" / f"{ident}.md").write_text(f"# {ident}\n\n::: {ident}\n", encoding="utf-8")
lines.append(f"- [{file}]({ident}.md)")
refs.update({(file, n): f"{ident}.{n}" for n in griffe_names(file)})
(src / "py" / "index.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
return refs
# ── 원고 ───────────────────────────────────────────────────────────────────
def anchor(fid: str) -> str:
return "f-" + hashlib.sha1(fid.encode("utf-8")).hexdigest()[:10]
def first_line(doc: str) -> str:
return next((s.strip() for s in (doc or "").splitlines() if s.strip()), "")
def owner_of(item: str, fns_by_id: dict, by_file: dict) -> dict | None:
"""세션 칸 · 서버 주소의 `id` · `파일:줄` · `파일` → 함수."""
if item in fns_by_id:
return fns_by_id[item]
if item[:3] in ("ts:", "py:"):
item = item[3:]
file, _, line = item.split("#")[0].partition(":")
cands = by_file.get(file, [])
if not cands:
return None
if line.isdigit():
before = [f for f in cands if f.get("line", 0) <= int(line)]
return before[-1] if before else cands[0]
return cands[0]
class Site:
def __init__(self, data: dict, rules: dict, out: Path):
self.data, self.rules, self.out = data, rules, out.resolve()
self.src = self.out / "src"
self.fns = data["functions"]
self.by_id = {f["id"]: f for f in self.fns}
self.by_file: dict[str, list] = {}
for f in sorted(self.fns, key=lambda f: f.get("line", 0)):
self.by_file.setdefault(f["file"], []).append(f)
self.gid: dict[str, str] = {}
self.missing_refs: list[str] = []
self.stale: list[str] = []
self.ts_pages: dict = {}
self.py_refs: dict = {}
# 링크
def fn_link(self, fid: str, label: str | None = None) -> str:
f = self.by_id.get(fid)
if f is None:
return f"`{label or fid.split('#')[-1]}`"
return f"[`{label or f['name']}`]({self.gid[fid]}.md#{anchor(fid)})"
def hand_refs(self, text: str, where: str) -> str:
def sub(m: re.Match) -> str:
ref = m.group(1).strip()
hit = [
f
for f in self.fns
if f["name"] == ref or f"{grouping.stem(f['file'])}#{f['name']}" == ref
]
if not hit:
self.missing_refs.append(f"{where}: {ref}")
return f"`{ref}` ⚠"
return self.fn_link(hit[0]["id"])
return REF.sub(sub, text)
def tool_link(self, f: dict) -> str:
if f["lang"] == "ts":
rel = self.ts_pages.get((f["file"], f["name"]))
return f"[자세히](../{rel})" if rel else ""
ref = self.py_refs.get((f["file"], f["name"]))
return f"[자세히][{ref}]" if ref else ""
# 함수 칸
def fn_block(self, f: dict) -> str:
params = ", ".join(
p["name"] if isinstance(p, dict) else str(p) for p in f.get("params", [])
)
bits = [f"`{f['file']}:{f.get('line', 0)}`"]
if params:
bits.append(f"입력 `{params}`")
if f.get("returns"):
bits.append(f"출력 `{f['returns']}`")
if link := self.tool_link(f):
bits.append(link)
lines = [
f"### `{f['name']}` {{ #{anchor(f['id'])} }}",
"",
first_line(f.get("doc")) or "*설명 없음*",
"",
]
lines += [" · ".join(bits), ""]
callers = f.get("called_by", [])
if callers:
shown = " · ".join(self.fn_link(c) for c in callers[:8])
more = f" 외 {len(callers) - 8}" if len(callers) > 8 else ""
lines += [f"부르는 곳: {shown}{more}", ""]
return "\n".join(lines)
def fn_graph(self, fns: list[dict]) -> str:
ids = {f["id"] for f in fns}
edges = [(f["id"], c) for f in fns for c in f.get("calls", []) if c in ids and c != f["id"]]
if not edges:
return ""
degree = Counter(a for e in edges for a in e)
keep = {i for i, _ in degree.most_common(MAX_GRAPH)}
node = {i: f"n{k}" for k, i in enumerate(sorted(keep))}
lines = ["```mermaid", "flowchart LR"]
lines += [f' {node[i]}["{self.by_id[i]["name"]}"]' for i in sorted(keep)]
lines += [
f" {node[a]} --> {node[b]}" for a, b in sorted(set(edges)) if a in keep and b in keep
]
lines.append("```")
note = f"\n\n많이 이어진 함수 {MAX_GRAPH} 개만 그림." if len(degree) > MAX_GRAPH else ""
return "\n".join(lines) + note
def group_uses(self, gid: str) -> tuple[list[str], list[str]]:
def mine(item: str) -> bool:
o = owner_of(item, self.by_id, self.by_file)
return o is not None and self.gid[o["id"]] == gid
keys = []
for k in self.data["session_keys"]:
reads, writes = (
[r for r in k.get("reads", []) if mine(r)],
[w for w in k.get("writes", []) if mine(w)],
)
if reads or writes:
keys.append(f"| `{k['key']}` | {len(reads)} | {len(writes)} |")
apis = []
for a in self.data["api"]:
calls = [c for c in a.get("callers", []) if mine(c)]
if mine(a.get("handler", "")) or calls:
handler = (
self.fn_link(a["handler"])
if a.get("handler") in self.by_id
else f"`{a.get('handler', '')}`"
)
apis.append(f"| `{a['method']} {a['path']}` | {handler} | {len(calls)} |")
return keys, apis
# 화면
def write_group(self, page: dict, g: dict, fns: list[dict], hand_dir: Path) -> str:
meta, body = read_hand(hand_dir / f"{g['id']}.md")
now = grouping.fingerprint(fns)
out = [f"# {g['title']}", ""]
if meta.get("지문") != now:
self.stale.append(g["id"])
out += [
'!!! warning "설명 확인 필요"',
f" 함수 목록이 바뀜(손글 지문 `{meta.get('지문', '없음')}` · 지금 `{now}`).",
"",
]
out += [self.hand_refs(body, f"groups/{g['id']}.md") or "*설명 없음*", ""]
if graph := self.fn_graph(fns):
out += ["## 묶음 안 흐름", "", graph, ""]
keys, apis = self.group_uses(g["id"])
if keys:
out += ["## 쓰는 세션 칸", "", "| 칸 | 읽는 곳 | 쓰는 곳 |", "|---|---|---|", *keys, ""]
if apis:
out += [
"## 서버 주소",
"",
"| 주소 | 받는 함수 | 부르는 곳 |",
"|---|---|---|",
*apis,
"",
]
out += [f"## 함수 {len(fns)}", "", *(self.fn_block(f) for f in fns)]
(self.src / page["id"] / f"{g['id']}.md").write_text("\n".join(out), encoding="utf-8")
return first_line(body)
def write_page(self, page: dict, groups: dict[str, list[dict]]) -> list:
hand_dir = DOCS / page["guide"]
(self.src / page["id"]).mkdir(parents=True, exist_ok=True)
cards, nav = [], [{"개요": f"{page['id']}/index.md"}]
for g in page["groups"]:
fns = groups[g["id"]]
lead = self.write_group(page, g, fns, hand_dir / "groups")
warn = " · ⚠ 설명 확인 필요" if g["id"] in self.stale else ""
cards += [
f"- **[{g['title']}]({g['id']}.md)** · 함수 {len(fns)}{warn}",
"",
f" {lead}",
"",
]
nav.append({g["title"]: f"{page['id']}/{g['id']}.md"})
none = groups[grouping.NONE]
none_md = [f"# 묶음 없음 {len(none)}", "", "groups.yml 규칙에 안 맞는 함수.", ""]
(self.src / page["id"] / "none.md").write_text(
"\n".join(none_md + [self.fn_block(f) for f in none]), encoding="utf-8"
)
nav.append({"묶음 없음": f"{page['id']}/none.md"})
_, body = read_hand(hand_dir / "page.md")
md = [
f"# {page['title']}",
"",
'<div class="grid cards" markdown>',
"",
*cards,
"</div>",
"",
]
md += [f"묶음 없음 함수 {len(none)} — [목록](none.md)", ""]
if graph := self.group_graph(page, groups):
md += ["## 묶음 사이 흐름", "", graph, ""]
md.append(self.hand_refs(body, "page.md"))
(self.src / page["id"] / "index.md").write_text("\n".join(md) + "\n", encoding="utf-8")
return nav
def group_graph(self, page: dict, groups: dict[str, list[dict]]) -> str:
edges = Counter(
(self.gid[f["id"]], self.gid[c])
for fns in groups.values()
for f in fns
for c in f.get("calls", [])
if c in self.gid and self.gid[c] != self.gid[f["id"]]
)
if not edges:
return ""
titles = {g["id"]: g["title"] for g in page["groups"]} | {grouping.NONE: "묶음 없음"}
used = sorted({a for e in edges for a in e})
lines = ["```mermaid", "flowchart LR"] + [f' {g}["{titles[g]}"]' for g in used]
lines += [f" {a} -->|{n}| {b}" for (a, b), n in sorted(edges.items())]
return "\n".join(lines + ["```"])
def build(self, typedoc: bool = True) -> dict:
if self.src.exists():
shutil.rmtree(self.src)
(self.src / "py").mkdir(parents=True)
files = sorted(self.by_file)
if typedoc:
self.ts_pages = run_typedoc([f for f in files if f.endswith(".ts")], self.out)
self.py_refs = write_py_pages([f for f in files if f.endswith(".py")], self.src)
nav = [{"처음": "index.md"}]
home = ["# Aislo 프로그램 문서", "", "페이지 → 로직 묶음 → 함수 차례로 눌러 내려감.", ""]
for page in self.rules["pages"]:
groups = grouping.assign(self.fns, self.data["registry"], page["groups"])
self.gid.update({f["id"]: gid for gid, fns in groups.items() for f in fns})
nav.append({page["title"]: self.write_page(page, groups)})
home.append(f"- [{page['title']}]({page['id']}/index.md)")
ts_nav = [{"TypeScript": "ts/index.md"}] if (self.src / "ts" / "index.md").exists() else []
nav.append({"함수 화면": [*ts_nav, {"파이썬": "py/index.md"}]})
(self.src / "index.md").write_text("\n".join(home) + "\n", encoding="utf-8")
(self.src / ".nav.json").write_text(json.dumps(nav, ensure_ascii=False), encoding="utf-8")
return self.report()
def report(self) -> dict:
exported = [f for f in self.fns if f.get("exported")]
return {
"functions": len(self.fns),
"groups": sum(len(p["groups"]) for p in self.rules["pages"]),
"ungrouped": sum(1 for v in self.gid.values() if v == grouping.NONE),
"session_keys": len(self.data["session_keys"]),
"api": len(self.data["api"]),
"undocumented_exports": sorted(
f["id"] for f in exported if not first_line(f.get("doc"))
),
"missing_refs": self.missing_refs,
"stale_groups": self.stale,
}
def build_site(out: Path, strict: bool = True) -> None:
"""mkdocs build(--strict) — docs_dir = out/src · site_dir = out/site."""
from mkdocs.commands.build import build
from mkdocs.config import load_config
cfg = load_config(
str(DOCS / "mkdocs.yml"),
docs_dir=str(out / "src"),
site_dir=str(out / "site"),
strict=strict,
)
build(cfg)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--data", type=Path, default=DOCS / "_data")
ap.add_argument("--out", type=Path, default=DOCS / "_build")
ap.add_argument("--site", action="store_true", help="mkdocs build --strict 까지")
ap.add_argument("--stamp", action="store_true", help="지금 함수 목록 지문을 손글에 적음")
args = ap.parse_args()
t0 = time.time()
data, rules = load_data(args.data), grouping.load_rules()
if args.stamp:
for page in rules["pages"]:
groups = grouping.assign(data["functions"], data["registry"], page["groups"])
for g in page["groups"]:
path = DOCS / page["guide"] / "groups" / f"{g['id']}.md"
if path.exists():
stamp(path, grouping.fingerprint(groups[g["id"]]))
print("지문 적음")
return
args.out.mkdir(parents=True, exist_ok=True)
out = args.out.resolve()
rep = Site(data, rules, out).build()
if args.site:
build_site(out)
summary = {k: (len(v) if isinstance(v, list) else v) for k, v in rep.items()}
print(json.dumps(summary, ensure_ascii=False), f"{time.time() - t0:.1f}s")
for ref in rep["missing_refs"]:
print("손글 함수 없음:", ref)
if __name__ == "__main__":
main()
+81
View File
@@ -0,0 +1,81 @@
"""함수 → 로직 묶음 자동 배정(PLAN 478-2).
규칙 = docs/tools/groups.yml · 위에서부터 첫 맞음:
registry — 계산 등록부 id 무늬 → 그 줄의 ts · pair 함수
files — 파일 이름(확장자 뺀) 무늬
어디에도 안 맞는 함수 = 「묶음 없음」(None).
"""
from __future__ import annotations
import hashlib
from fnmatch import fnmatchcase
from pathlib import Path, PurePosixPath
import yaml
TOOLS = Path(__file__).resolve().parent
NONE = "none"
def load_rules(path: Path = TOOLS / "groups.yml") -> dict:
return yaml.safe_load(path.read_text(encoding="utf-8"))
def stem(file: str) -> str:
return PurePosixPath(file.replace("\\", "/")).stem
def registry_ref_id(ref: str | None) -> str | None:
"""등록부 칸 `<경로>#<함수>` → 함수 id."""
if not ref or "#" not in ref:
return None
lang = "py" if ref.split("#", 1)[0].endswith(".py") else "ts"
return f"{lang}:{ref}"
def registry_owners(registry: list[dict], groups: list[dict]) -> dict[str, str]:
"""등록부로 정해지는 함수 id → 묶음 id."""
owners: dict[str, str] = {}
for row in registry:
rid = row.get("id", "")
group = next(
(g["id"] for g in groups if any(fnmatchcase(rid, p) for p in g.get("registry", []))),
None,
)
if group is None:
continue
for key in ("ts", "pair"):
fid = registry_ref_id(row.get(key))
if fid:
owners.setdefault(fid, group)
return owners
def file_group(file: str, groups: list[dict]) -> str | None:
name = stem(file)
for g in groups:
if any(fnmatchcase(name, p) for p in g.get("files", [])):
return g["id"]
return None
def assign(
functions: list[dict], registry: list[dict], groups: list[dict]
) -> dict[str, list[dict]]:
"""묶음 id → 함수 목록(파일 · 줄 차례) · 「묶음 없음」 = NONE."""
owners = registry_owners(registry, groups)
out: dict[str, list[dict]] = {g["id"]: [] for g in groups}
out[NONE] = []
for fn in functions:
gid = owners.get(fn["id"]) or file_group(fn["file"], groups) or NONE
out[gid].append(fn)
for fns in out.values():
fns.sort(key=lambda f: (f["file"], f.get("line", 0)))
return out
def fingerprint(fns: list[dict]) -> str:
"""함수 목록 지문 — 손글에 적힌 지문과 다르면 「설명 확인 필요」."""
text = "\n".join(sorted(f["id"] for f in fns))
return hashlib.sha1(text.encode("utf-8")).hexdigest()[:10]
+218
View File
@@ -0,0 +1,218 @@
# 문서 사이트 묶음 규칙 — 페이지 · 범위 · 로직 묶음
# 함수 하나 = 묶음 하나 · 위에서부터 첫 맞음
# registry — 계산 등록부 id 무늬(그 줄의 ts · pair 함수)
# files — 파일 이름 무늬(확장자 뺀 이름 · fnmatch)
# 어디에도 안 맞음 = 「묶음 없음」
pages:
- id: B05_B06
title: B05 · B06 종단 · 횡단 설계
guide: guide/B05_B06
scope:
- B05_Profile/
- B06_Section/
- A00_Common/design/
- common_util/
groups:
- id: recommend
title: 추천 노선
files:
- "*RouteInitial*"
- B05_Profile_UI_RouteEdit_Initial*
- B05_Profile_UI_RouteEdit_Recommend*
- B05_Profile_UI_Panel_Recommend
- B05_Profile_Engine_InitialProfile
- B05_Profile_Engine_RidgeValley*
- B05_Profile_Engine_Skeleton
- B05_Profile_Engine_Solver
- id: route_edit
title: 노선 편집
registry: ["route.*"]
files:
- B05_Profile_UI_RouteEdit*
- B05_Profile_RouteEdit_*
- B05_Profile_Route_NodeEdits
- B05_Profile_ContourLock
- design_plan_peek
- common_util_route_polyline
- common_util_route_geometry
- id: route_save
title: 노선 저장 · 시설 따라감
registry: ["stations.carry", "facility.follow*", "facility.plus_follow"]
files:
- B05_Profile_Route_Save*
- B05_Profile_Router_Replan
- B05_Profile_Api_Replan
- B06_Section_Router_DesignSave_Route
- B06_Section_Route_Carry*
- B05_Profile_Pipeline_Follow
- B05_Profile_Structure_Follow
- design_calc_node_carry
- design_calc_node_follow
- common_util_route_follow
- id: stations
title: 측점
registry: ["stations.*"]
files:
- B05_Profile_Sections_Stations
- B05_Profile_Engine_Sections*
- B05_Profile_UI_Page_Stations
- B05_Profile_UI_IrregularStations
- B05_Profile_Util_Station
- B05_Profile_Plus_Stations
- B05_Profile_Api_PlusStations
- B06_Section_Station_*
- B06_Section_Swap_Bus
- B06_Section_Router_Stations
- B06_Section_UI_Missing_Stations
- B06_Section_UI_Page_Station_Swap
- design_pipeline_stations
- design_pipeline_probe_station
- common_util_station_key
- id: ground
title: 지반 표본
registry: ["ground.*"]
files:
- B05_Profile_Api_TerrainGrid
- B05_Profile_Sections_Ground
- B05_Profile_Pipeline_RouteGround
- B05_Profile_Router_Terrain
- B06_Section_Engine_SampleExtend
- B06_Section_Router_Widen
- common_util_surface_sampler
- id: earthwork
title: 토량 · 유토 배분
registry: ["earth.*", "haul.*"]
files:
- "*MassHaul*"
- "*HaulPlan*"
- B05_Profile_UI_Profile_Balance
- common_util_mass_haul*
- id: profile
title: 종단 계획고
registry: ["profile.*"]
files:
- B05_Profile_UI_Profile_*
- B05_Profile_Profile_*
- B05_Profile_Grade_*
- B05_Profile_Engine_Grade*
- B05_Profile_Pipeline_Grade
- B05_Profile_EndOffsets
- B05_Profile_UI_Panel*
- B05_Profile_PageSettings
- B05_Profile_Api_PageSettings
- design_profile*
- id: cross
title: 횡단 설계 계산
registry: ["cross.*"]
files:
- B06_Section_Cross_*
- B06_Section_Engine_*
- B06_Section_Design_Keys
- B06_Section_Router_Design
- B06_Section_Router_Design_Ranges
- B06_Section_Router_Regenerate_Options
- B06_Section_Server_Calc*
- common_util_cross_*
- id: cross_view
title: 횡단 화면 · 조정창
files:
- B06_Section_UI_Cross_*
- B06_Section_UI_Standard*
- B06_Section_UI_Section_*
- B06_Section_UI_Longitudinal
- B06_Section_UI_Adjust_Dock
- B06_Section_Label_Tiers
- B06_Section_Display_Width
- B06_Section_Session_Cells
- B06_Section_Section_Adjusts
- B06_Section_UI_Page_Design_Sync
- B06_Section_UI_Page_Station_Controls
- B06_Section_UI_Page_Span_Control
- B06_Section_UI_Page_Ford_Controls
- B06_Section_UI_Page_Link_Session
- B06_Section_UI_Page_Pipe_Options
- B06_Section_UI_Page_Patches
- id: drainage
title: 관 · 배수
registry: ["drainage.*", "facility.pipe*"]
files:
- "*Drainage*"
- B05_Profile_Api_Pipes_Draft
- B06_Section_Api_Culvert_Options
- design_pipe_ids
- design_structure_pipe*
- common_util_drainage*
- common_util_culvert*
- id: structures
title: 구조물
registry: ["facility.*"]
files:
- "*Structures*"
- "*Structure_*"
- B05_Profile_UI_Structure_Pick_Session
- B06_Section_Structure_Layouts
- design_structure*
- design_facility_lane
- common_util_structure_*
- common_util_berm*
- common_util_excavation
- id: corridor
title: 3D 길 모형
registry: ["corridor.*", "view3d.*"]
files:
- "*Corridor*"
- B05_Profile_UI_Viewer*
- design_view_profile_3d*
- id: footprint
title: 평면 발자국
registry: ["plan.*", "footprint.*"]
files:
- "*Footprint*"
- id: pipeline
title: 계산 묶음 · 가게
files:
- design_pipeline*
- design_calc*
- design_store
- B05_Profile_Pipeline_*
- B06_Section_Section_*
- B06_Section_UI_Page_Persist
- common_util_calc_runner
- id: save
title: 저장 · 확정 · 초기화 · 떠남
files:
- design_save*
- design_reset
- design_leave
- design_shared
- B06_Section_Router_DesignSave
- B06_Section_Router_Confirm
- B05_Profile_Router_Confirm
- B05_Profile_Router_Lifecycle
- common_util_initial_snapshot
- common_util_save_guard
- id: screen
title: 페이지 틀 · 보기
files:
- design_page*
- design_view*
- design_panel
- design_dock
- design_pick*
- design_selection
- design_user_pick
- design_sections
- B05_Profile_UI*
- B06_Section_UI*
- id: server
title: 서버 창구 · 저장소
files:
- "*_Router*"
- "*_Repository*"
- "*_Schema"
- "*_Api_*"
- B05_Profile_Engine*
- B05_Profile
- B06_Section
- B05_Profile_Debug
+11
View File
@@ -0,0 +1,11 @@
"""mkdocs 훅 — build.py 가 쓴 왼쪽 메뉴(<docs_dir>/.nav.json)를 얹음."""
import json
from pathlib import Path
def on_config(config):
nav = Path(config.docs_dir) / ".nav.json"
if nav.exists():
config.nav = json.loads(nav.read_text(encoding="utf-8"))
return config
+356
View File
@@ -0,0 +1,356 @@
{
"name": "aislo-docs-tools",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "aislo-docs-tools",
"dependencies": {
"ts-morph": "^28.0.0",
"typedoc": "^0.28.20",
"typedoc-plugin-markdown": "^4.14.0",
"typescript": "6.0"
}
},
"node_modules/@gerrit0/mini-shiki": {
"version": "3.23.0",
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz",
"integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==",
"license": "MIT",
"dependencies": {
"@shikijs/engine-oniguruma": "^3.23.0",
"@shikijs/langs": "^3.23.0",
"@shikijs/themes": "^3.23.0",
"@shikijs/types": "^3.23.0",
"@shikijs/vscode-textmate": "^10.0.2"
}
},
"node_modules/@shikijs/engine-oniguruma": {
"version": "3.23.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz",
"integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.23.0",
"@shikijs/vscode-textmate": "^10.0.2"
}
},
"node_modules/@shikijs/langs": {
"version": "3.23.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz",
"integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.23.0"
}
},
"node_modules/@shikijs/themes": {
"version": "3.23.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz",
"integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.23.0"
}
},
"node_modules/@shikijs/types": {
"version": "3.23.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz",
"integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
}
},
"node_modules/@shikijs/vscode-textmate": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
"license": "MIT"
},
"node_modules/@ts-morph/common": {
"version": "0.29.0",
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.29.0.tgz",
"integrity": "sha512-35oUmphHbJvQ/+UTwFNme/t2p3FoKiGJ5auTjjpNTop2dyREspirjMy82PLSC1pnDJ8ah1GU98hwpVt64YXQsg==",
"license": "MIT",
"dependencies": {
"minimatch": "^10.0.1",
"path-browserify": "^1.0.1",
"tinyglobby": "^0.2.14"
}
},
"node_modules/@types/hast": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
"integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
"license": "MIT",
"dependencies": {
"@types/unist": "*"
}
},
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/brace-expansion": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/code-block-writer": {
"version": "13.0.3",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz",
"integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==",
"license": "MIT"
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/linkify-it": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz",
"integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/markdown-it"
}
],
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"license": "MIT"
},
"node_modules/markdown-it": {
"version": "14.3.3",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.3.tgz",
"integrity": "sha512-L//tDAkjTjJCU6pBUSBTgs3Kg4InVaZuUZog/b6V12UKhcDQ7S78877ibbCanRExwPuYP1CZD7Gd2dxSy1GXRg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/markdown-it"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.5.0",
"linkify-it": "^5.0.2",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/mdurl": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz",
"integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==",
"license": "MIT"
},
"node_modules/minimatch": {
"version": "10.2.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.8"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"license": "MIT"
},
"node_modules/picomatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/ts-morph": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-28.0.0.tgz",
"integrity": "sha512-Wp3tnZ2bzwxyTZMtgWVzXDfm7lB1Drz+y9DmmYH/L702PQhPyVrp3pkou3yIz4qjS14GY9kcpmLiOOMvl8oG1g==",
"license": "MIT",
"dependencies": {
"@ts-morph/common": "~0.29.0",
"code-block-writer": "^13.0.3"
}
},
"node_modules/typedoc": {
"version": "0.28.20",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.20.tgz",
"integrity": "sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg==",
"license": "Apache-2.0",
"dependencies": {
"@gerrit0/mini-shiki": "^3.23.0",
"lunr": "^2.3.9",
"markdown-it": "^14.3.0",
"minimatch": "^10.2.5",
"yaml": "^2.9.0"
},
"bin": {
"typedoc": "bin/typedoc"
},
"engines": {
"node": ">= 18",
"pnpm": ">= 10"
},
"peerDependencies": {
"typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x"
}
},
"node_modules/typedoc-plugin-markdown": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.14.0.tgz",
"integrity": "sha512-Y2a3oDV9ekrbkk6RBSGH8GyNkh0xIdemIOOTr3tiRN/33nvFGStOs9nw+6Q84gF1XFkcVZrgM4Ckcsf6e/YpKQ==",
"license": "MIT",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"typedoc": "0.28.x"
}
},
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/yaml": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz",
"integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
}
}
}
+1
View File
@@ -0,0 +1 @@
{"name":"aislo-docs-tools","private":true,"type":"module","dependencies":{"ts-morph":"^28.0.0","typedoc":"^0.28.20","typedoc-plugin-markdown":"^4.14.0","typescript":"6.0"}}
+4
View File
@@ -0,0 +1,4 @@
mkdocs>=1.6
mkdocs-material>=9.5
mkdocstrings[python]>=0.26
pyyaml>=6
@@ -0,0 +1,10 @@
[
{
"method": "POST",
"path": "/route/node-edits",
"handler": "py:B05_Profile/B05_Profile_Route_NodeEdits.py#node_edits",
"callers": [
"ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes"
]
}
]
@@ -0,0 +1,407 @@
[
{
"id": "ts:common_util/common_util_station_key.ts#chainageKey",
"lang": "ts",
"file": "common_util/common_util_station_key.ts",
"line": 24,
"name": "chainageKey",
"exported": true,
"doc": "누가거리 → 측점 열쇠 글.",
"params": [
{
"name": "chainageM",
"type": ""
}
],
"returns": "string",
"calls": [],
"called_by": [
"ts:common_util/common_util_station_key.ts#stationKey"
]
},
{
"id": "ts:common_util/common_util_station_key.ts#stationKey",
"lang": "ts",
"file": "common_util/common_util_station_key.ts",
"line": 32,
"name": "stationKey",
"exported": true,
"doc": "측점 열쇠(grid:k · pipe: · structure:).",
"params": [
{
"name": "kind",
"type": ""
},
{
"name": "chainageM",
"type": ""
}
],
"returns": "string",
"calls": [
"ts:common_util/common_util_station_key.ts#chainageKey"
],
"called_by": [
"ts:common_util/common_util_station_key.ts#dedupeStationKeys"
]
},
{
"id": "ts:common_util/common_util_station_key.ts#dedupeStationKeys",
"lang": "ts",
"file": "common_util/common_util_station_key.ts",
"line": 54,
"name": "dedupeStationKeys",
"exported": true,
"doc": "",
"params": [
{
"name": "keys",
"type": ""
},
{
"name": "chainages",
"type": ""
}
],
"returns": "string[]",
"calls": [
"ts:common_util/common_util_station_key.ts#stationKey"
],
"called_by": []
},
{
"id": "ts:common_util/common_util_station_key.ts#looseChainage",
"lang": "ts",
"file": "common_util/common_util_station_key.ts",
"line": 67,
"name": "looseChainage",
"exported": true,
"doc": "",
"params": [
{
"name": "key",
"type": ""
}
],
"returns": "number | null",
"calls": [],
"called_by": []
},
{
"id": "ts:common_util/common_util_station_key.ts#curveMoves",
"lang": "ts",
"file": "common_util/common_util_station_key.ts",
"line": 80,
"name": "curveMoves",
"exported": true,
"doc": "",
"params": [
{
"name": "before",
"type": ""
},
{
"name": "after",
"type": ""
}
],
"returns": "Map",
"calls": [],
"called_by": []
},
{
"id": "ts:common_util/common_util_station_key.ts#sameSeat",
"lang": "ts",
"file": "common_util/common_util_station_key.ts",
"line": 116,
"name": "sameSeat",
"exported": true,
"doc": "같은 자리 판정.",
"params": [
{
"name": "a",
"type": ""
},
{
"name": "b",
"type": ""
}
],
"returns": "boolean",
"calls": [],
"called_by": [
"ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes"
]
},
{
"id": "ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#median",
"lang": "ts",
"file": "B05_Profile/logic/B05_Profile_Grade_Seat.ts",
"line": 24,
"name": "median",
"exported": false,
"doc": "",
"params": [
{
"name": "values",
"type": ""
}
],
"returns": "number",
"calls": [],
"called_by": [
"ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes"
]
},
{
"id": "ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes",
"lang": "ts",
"file": "B05_Profile/logic/B05_Profile_Grade_Seat.ts",
"line": 32,
"name": "seatBornNodes",
"exported": true,
"doc": "새 측점에 원지반을 지나는 꼭짓점.",
"params": [
{
"name": "base",
"type": ""
},
{
"name": "born",
"type": ""
},
{
"name": "held",
"type": ""
}
],
"returns": "AlignmentNode[]",
"calls": [
"ts:B05_Profile/logic/B05_Profile_Grade_Seat.ts#median",
"ts:common_util/common_util_station_key.ts#sameSeat"
],
"called_by": []
},
{
"id": "py:common_util/common_util_station_key.py#_half_up",
"lang": "py",
"file": "common_util/common_util_station_key.py",
"line": 22,
"name": "_half_up",
"exported": false,
"doc": "",
"params": [
{
"name": "value",
"type": ""
}
],
"returns": "int",
"calls": [],
"called_by": []
},
{
"id": "py:common_util/common_util_station_key.py#chainage_key",
"lang": "py",
"file": "common_util/common_util_station_key.py",
"line": 27,
"name": "chainage_key",
"exported": true,
"doc": "누가거리 → 측점 열쇠 글.",
"params": [
{
"name": "chainage_m",
"type": ""
}
],
"returns": "str",
"calls": [],
"called_by": []
},
{
"id": "py:common_util/common_util_station_key.py#station_key",
"lang": "py",
"file": "common_util/common_util_station_key.py",
"line": 32,
"name": "station_key",
"exported": true,
"doc": "",
"params": [
{
"name": "kind",
"type": ""
},
{
"name": "chainage_m",
"type": ""
}
],
"returns": "str",
"calls": [],
"called_by": []
},
{
"id": "py:common_util/common_util_station_key.py#dedupe_station_keys",
"lang": "py",
"file": "common_util/common_util_station_key.py",
"line": 68,
"name": "dedupe_station_keys",
"exported": true,
"doc": "",
"params": [
{
"name": "keys",
"type": ""
},
{
"name": "chainages",
"type": ""
}
],
"returns": "list[str]",
"calls": [],
"called_by": []
},
{
"id": "py:common_util/common_util_station_key.py#loose_chainage",
"lang": "py",
"file": "common_util/common_util_station_key.py",
"line": 79,
"name": "loose_chainage",
"exported": true,
"doc": "",
"params": [
{
"name": "key",
"type": ""
}
],
"returns": "float | None",
"calls": [],
"called_by": []
},
{
"id": "py:common_util/common_util_station_key.py#curve_moves",
"lang": "py",
"file": "common_util/common_util_station_key.py",
"line": 89,
"name": "curve_moves",
"exported": true,
"doc": "",
"params": [
{
"name": "before",
"type": ""
},
{
"name": "after",
"type": ""
}
],
"returns": "dict[str, str]",
"calls": [],
"called_by": []
},
{
"id": "py:common_util/common_util_station_key.py#station_seat",
"lang": "py",
"file": "common_util/common_util_station_key.py",
"line": 117,
"name": "station_seat",
"exported": true,
"doc": "",
"params": [
{
"name": "station",
"type": ""
}
],
"returns": "tuple | None",
"calls": [],
"called_by": []
},
{
"id": "py:common_util/common_util_station_key.py#same_seat",
"lang": "py",
"file": "common_util/common_util_station_key.py",
"line": 130,
"name": "same_seat",
"exported": true,
"doc": "",
"params": [
{
"name": "a",
"type": ""
},
{
"name": "b",
"type": ""
}
],
"returns": "bool",
"calls": [],
"called_by": []
},
{
"id": "py:B05_Profile/B05_Profile_Route_NodeEdits.py#node_edits",
"lang": "py",
"file": "B05_Profile/B05_Profile_Route_NodeEdits.py",
"line": 21,
"name": "node_edits",
"exported": true,
"doc": "노드 편집 규칙.",
"params": [
{
"name": "nodes",
"type": ""
}
],
"returns": "dict",
"calls": [],
"called_by": []
},
{
"id": "py:common_util/common_util_atomic.py#atomic_write_bytes",
"lang": "py",
"file": "common_util/common_util_atomic.py",
"line": 10,
"name": "atomic_write_bytes",
"exported": true,
"doc": "",
"params": [
{
"name": "path",
"type": ""
},
{
"name": "payload",
"type": ""
}
],
"returns": "None",
"calls": [],
"called_by": []
},
{
"id": "py:common_util/common_util_atomic.py#atomic_write_npz",
"lang": "py",
"file": "common_util/common_util_atomic.py",
"line": 34,
"name": "atomic_write_npz",
"exported": true,
"doc": "",
"params": [
{
"name": "path",
"type": ""
},
{
"name": "arrays",
"type": ""
}
],
"returns": "None",
"calls": [],
"called_by": []
}
]
@@ -0,0 +1,18 @@
[
{
"id": "stations.key",
"no": "R7",
"label": "측점 열쇠",
"site": "browser_now",
"ts": "common_util/common_util_station_key.ts#stationKey",
"pair": "common_util/common_util_station_key.py#station_key"
},
{
"id": "profile.seat",
"no": "P6",
"label": "옮긴 시설 꼭짓점 세움",
"site": "browser_now",
"ts": "B05_Profile/logic/B05_Profile_Grade_Seat.ts#seatBornNodes",
"pair": null
}
]
@@ -0,0 +1,11 @@
[
{
"key": "aislo:draft:route-draft",
"reads": [
"ts:common_util/common_util_station_key.ts#stationKey"
],
"writes": [
"B05_Profile/logic/B05_Profile_Grade_Seat.ts:32"
]
}
]
+129
View File
@@ -0,0 +1,129 @@
"""478-2 문서 사이트 검사 — 묶음 배정 · 손글 함수 · 함수 화면 도구 대 추출 · mkdocs --strict.
가짜 추출(fixtures/478_docs)은 늘 돎 · 진짜 추출(docs/_data)은 있을 때만.
"""
import json
import re
import sys
import warnings
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "docs" / "tools"))
import build # noqa: E402
import group # noqa: E402
FIXTURE = Path(__file__).parent / "fixtures" / "478_docs"
REAL = ROOT / "docs" / "_data"
def make_site(data_dir: Path, out: Path) -> tuple[build.Site, dict]:
site = build.Site(build.load_data(data_dir), group.load_rules(), out)
return site, site.build()
def shown_anchors(site: build.Site) -> set[str]:
text = "".join(p.read_text(encoding="utf-8") for p in (site.src).glob("*/*.md"))
return set(re.findall(r"\{ #(f-[0-9a-f]{10}) \}", text))
def typedoc_functions(out: Path) -> set[tuple[str, str]]:
project = json.loads((out / "typedoc.json").read_text(encoding="utf-8"))
modules = [m for m in project.get("children", []) if m["kind"] == 2] or [project]
return {
(c["sources"][0]["fileName"], c["name"])
for m in modules
for c in m.get("children", [])
if c["kind"] == 64
}
@pytest.fixture(scope="module")
def fake(tmp_path_factory):
return make_site(FIXTURE, tmp_path_factory.mktemp("fake"))
@pytest.fixture(scope="module")
def real(tmp_path_factory):
if not (REAL / "functions.json").exists():
pytest.skip("추출 없음 — docs/_data")
return make_site(REAL, tmp_path_factory.mktemp("real"))
def test_assign_rules():
data = build.load_data(FIXTURE)
page = group.load_rules()["pages"][0]
gid = {
f["name"]: g
for g, fns in group.assign(data["functions"], data["registry"], page["groups"]).items()
for f in fns
}
assert gid["stationKey"] == "stations" # 등록부 stations.key
assert gid["station_key"] == "stations" # 등록부 pair
assert gid["seatBornNodes"] == "profile" # 등록부 profile.seat
assert gid["median"] == "profile" # 파일 B05_Profile_Grade_*
assert gid["node_edits"] == "route_edit" # 파일 B05_Profile_Route_NodeEdits
assert gid["atomic_write_bytes"] == group.NONE
def test_fake_site(fake):
site, rep = fake
assert shown_anchors(site) == {build.anchor(f["id"]) for f in site.fns}
assert rep["ungrouped"] == 2
assert rep["session_keys"] == 1 and rep["api"] == 1
assert "stations" in rep["stale_groups"] # 손글 지문 ≠ 가짜 목록
build.build_site(site.out, strict=True)
def test_fake_tools_match_extract(fake):
site, _ = fake
ts = {(f["file"], f["name"]) for f in site.fns if f["lang"] == "ts" and f["exported"]}
assert typedoc_functions(site.out) == ts
py = {(f["file"], f["name"]) for f in site.fns if f["lang"] == "py"}
assert {(file, n) for file, _ in py for n in build.griffe_names(file)} == py
def test_real_hand_refs(real):
_, rep = real
assert rep["missing_refs"] == [], "손글이 가리키는 함수가 추출에 없음"
def test_real_every_function_shown(real):
site, _ = real
assert shown_anchors(site) == {build.anchor(f["id"]) for f in site.fns}, "조용히 빠진 함수"
def test_real_typedoc_matches_extract(real):
site, _ = real
ts = {(f["file"], f["name"]) for f in site.fns if f["lang"] == "ts" and f["exported"]}
got = typedoc_functions(site.out)
assert not (ts - got) and not (got - ts), (
f"추출만 {sorted(ts - got)[:10]} · TypeDoc 만 {sorted(got - ts)[:10]}"
)
def test_real_griffe_matches_extract(real):
site, _ = real
py = {(f["file"], f["name"]) for f in site.fns if f["lang"] == "py"}
got = {(file, n) for file in {f for f, _ in py} for n in build.griffe_names(file)}
assert not (py - got) and not (got - py), (
f"추출만 {sorted(py - got)[:10]} · griffe 만 {sorted(got - py)[:10]}"
)
def test_real_counts(real):
_, rep = real
if rep["undocumented_exports"]:
warnings.warn(f"설명 없는 export 함수 {len(rep['undocumented_exports'])}")
if rep["ungrouped"]:
warnings.warn(f"묶음 없는 함수 {rep['ungrouped']}")
assert rep["ungrouped"] < rep["functions"]
def test_real_strict_build(real):
site, _ = real
build.build_site(site.out, strict=True)