diff --git a/docs/guide/B05_B06/calculations.md b/docs/guide/B05_B06/calculations.md deleted file mode 100644 index c24929aaf..000000000 --- a/docs/guide/B05_B06/calculations.md +++ /dev/null @@ -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 | 손으로 맞춘 목록 둘 · 거울 없음 | diff --git a/docs/guide/B05_B06/groups/corridor.md b/docs/guide/B05_B06/groups/corridor.md new file mode 100644 index 000000000..3309eca4a --- /dev/null +++ b/docs/guide/B05_B06/groups/corridor.md @@ -0,0 +1,3 @@ +3D 길 모형 — 종단 · 횡단으로 길 몸체를 지음. + +- 노선 초안 · 종단 고침 동안 낡음 표시 · [저장] 뒤 서버 Node 가 다시 지음. diff --git a/docs/guide/B05_B06/groups/cross.md b/docs/guide/B05_B06/groups/cross.md new file mode 100644 index 000000000..7e7d768b0 --- /dev/null +++ b/docs/guide/B05_B06/groups/cross.md @@ -0,0 +1,5 @@ +횡단 설계선 · 면적 계산 — 브라우저 즉시 · [저장] 때 서버 Node 정본. + +- 설계선 = [[computeCrossDesign]] ↔ 파이썬 짝 · 단면적은 표준 · 구조물 측점 모두 Node. +- 다시 셈 입력 물러섬 = 세션 고름 → 저장 정본 설계 → config 기본값. +- 소단 구간 = 구조물에서 [[bermSpansFromStructures]] ↔ 서버 [[load_berm_spans]]. diff --git a/docs/guide/B05_B06/groups/cross_view.md b/docs/guide/B05_B06/groups/cross_view.md new file mode 100644 index 000000000..774bc174a --- /dev/null +++ b/docs/guide/B05_B06/groups/cross_view.md @@ -0,0 +1,5 @@ +횡단 화면 — 카드 단추 · 조정창 · 반폭 · 표준횡단 · 상세 표. + +- 카드 단추 = 세션 `crossdesign` 의 바꾼 칸만 · 메모리 상세 설계를 안 덮음. +- 조정창 칸 · 암선 · 암 경사 = 세션 칸이 값 주인(메모리 사본 없음) · 빈 칸 = 칸 지움. +- 표준횡단 = 저장분 · config 와 다를 때만 칸([[effectiveStandardCross]]). diff --git a/docs/guide/B05_B06/groups/drainage.md b/docs/guide/B05_B06/groups/drainage.md new file mode 100644 index 000000000..f6577f5f7 --- /dev/null +++ b/docs/guide/B05_B06/groups/drainage.md @@ -0,0 +1,5 @@ +관 · 배수 — 배수판 관 편집 · 배수유역 · 관 옵션. + +- 관 주인 = 가게 `pipes` · 배수 편집기는 숨어도 가게를 따름. +- 관 자리 읽기 = 지금 노선에 얹은 관 지점 한 벌([[route_pipe_points]]) · 관 파일 누가거리를 그대로 읽지 않음. +- [저장] = 관 차례 `pipe_points` PUT · 관 옵션 예약 있으면 같은 파일 한 번 더. diff --git a/docs/guide/B05_B06/groups/earthwork.md b/docs/guide/B05_B06/groups/earthwork.md new file mode 100644 index 000000000..5594aca1a --- /dev/null +++ b/docs/guide/B05_B06/groups/earthwork.md @@ -0,0 +1,4 @@ +누가토량 · 유토 배분 · 운반거리. + +- 누가토량 = TS 한 벌(브라우저 · Node) · 그릴 때 셈(접혀도 셈). +- 유토 배분 = 서버 Node(`/haul-plan`) · 저장본 `_derived/haul` · 입력 지문 다를 때만 다시 셈. diff --git a/docs/guide/B05_B06/groups/footprint.md b/docs/guide/B05_B06/groups/footprint.md new file mode 100644 index 000000000..07cc730df --- /dev/null +++ b/docs/guide/B05_B06/groups/footprint.md @@ -0,0 +1,3 @@ +평면 발자국 — 횡단 결과로 평면에 깎기 · 쌓기 테두리. + +- 묶음 마지막 단계 · B07 은 따로(파이썬) · 거울 시험. diff --git a/docs/guide/B05_B06/groups/ground.md b/docs/guide/B05_B06/groups/ground.md new file mode 100644 index 000000000..3e58e3aa4 --- /dev/null +++ b/docs/guide/B05_B06/groups/ground.md @@ -0,0 +1,4 @@ +측점마다 원지반 표본 — 브라우저 지형 격자 · 서버 npz. + +- 표본 짝 = 브라우저 격자 ↔ 서버 `sample_xy`(거울 시험) · 입력 선이 달라 mm 차. +- 노선 초안 없는 차례 = 정본 표본 유지 · 넓힘(표본 폭 넘음) = 저장 밖 서버. diff --git a/docs/guide/B05_B06/groups/pipeline.md b/docs/guide/B05_B06/groups/pipeline.md new file mode 100644 index 000000000..ae9d6e444 --- /dev/null +++ b/docs/guide/B05_B06/groups/pipeline.md @@ -0,0 +1,6 @@ +계산 묶음 · 설계 가게 · 상세 가게 — 입력 → 파생 한 방향. + +- 묶음 단계 = 노선 → 따라감 → 측점 → 지반 → 첫 계획선 → 종단 → 횡단 → 발자국 · 차례 번호 · 옛 차례 버림. +- 묶음 결과 내보냄 = [[publishPagePipeline]] · 조용한 차례 끝 = 가게 측점 = 지금 상세 측점. +- 설계 가게 = [[useDesignStore]] · 멈춘 뒤 1 s 세션 · 상세 가게 = [[loadSectionDetail]] · 새로고침 = [[checkSessionCanon]]. +- 서버 Node 입구 = 계산 등록부 id → 함수 · 파이썬은 [[run_calc]]. diff --git a/docs/guide/B05_B06/groups/profile.md b/docs/guide/B05_B06/groups/profile.md new file mode 100644 index 000000000..77d773762 --- /dev/null +++ b/docs/guide/B05_B06/groups/profile.md @@ -0,0 +1,6 @@ +종단 계획고 — ▲▼ · 직선화 · 자유지정 · 쉬프트 · 종단 R · 시종점 · 페이지 설정. + +- 편집값 주인 = 가게 `alignmentDraft`(세션 `alignment-draft`) · 지금 값 = 초안 ?? 서버 저장분. +- 계획선 짓기 [[buildAlignment]] ↔ 파이썬 짝(거울 시험). +- 짝 있는 옮긴 측점 = 꼭짓점 따라감([[followMovedNodes]]) · 짝 없는 새 측점 = 꼭짓점 안 세움. +- [저장] = 편집 델타만 보내고 서버가 종단 다시 셈. diff --git a/docs/guide/B05_B06/groups/recommend.md b/docs/guide/B05_B06/groups/recommend.md new file mode 100644 index 000000000..009d818bb --- /dev/null +++ b/docs/guide/B05_B06/groups/recommend.md @@ -0,0 +1,4 @@ +추천 노선 후보를 서버가 찾고 화면이 보여 줌 · 고른 후보를 [적용]. + +- 후보 초기 종단 = [적용] 체인과 같은 조각을 메모리에서 셈 · 저장본 `route_recommend/initial`. +- [적용] = 저장 밖 · 즉시 [[replan_route]] · 종단 = 새 규칙 초기 종단 · 옛 손 입력 안 넘김 · 초기값 다시 뜸 · 초안 전부 지움. diff --git a/docs/guide/B05_B06/groups/route_edit.md b/docs/guide/B05_B06/groups/route_edit.md new file mode 100644 index 000000000..5c4534989 --- /dev/null +++ b/docs/guide/B05_B06/groups/route_edit.md @@ -0,0 +1,5 @@ +평면 노선 편집기 — 교점 끌기 · 방향키 · 교점 추가 · 삭제 · 곡선 R · L · 스냅. + +- 조작은 편집기 메모리 → 놓을 때 세션 `route-draft` · 첫 초안이면 초안 전 상세를 hold. +- 곡선 선 = [[buildEditedPolyline]] ↔ 파이썬 짝 [[build_planned_polyline]] · 선 자리 x · y 4자리 한 벌. +- 노선 초안 차례 = 새 · 옮긴 측점만 브라우저 지반 표본. diff --git a/docs/guide/B05_B06/groups/route_save.md b/docs/guide/B05_B06/groups/route_save.md new file mode 100644 index 000000000..431fea4b0 --- /dev/null +++ b/docs/guide/B05_B06/groups/route_save.md @@ -0,0 +1,5 @@ +노선이 바뀐 [저장] — 새 노선 짜기 · 관 · 구조물 · 추가 측점 따라감 · 횡단 고침 넘김. + +- 서버 [[run_route_save_chain]] = 노선 · 측점 · 표본 · 횡단을 처음부터 · 전부 아니면 없음. +- 관 · 구조물 초안 다시 얹기 = [[rebasePipeDraft]] · [[rebaseStructureDraft]](좌표 따라감). +- 횡단 고침 넘김 = [[carryKeyedStates]] ↔ 서버 [[carry_cross_inputs]] · 자리 칸은 같은 자리([[sameSeat]]) 측점만. diff --git a/docs/guide/B05_B06/groups/save.md b/docs/guide/B05_B06/groups/save.md new file mode 100644 index 000000000..2c2071272 --- /dev/null +++ b/docs/guide/B05_B06/groups/save.md @@ -0,0 +1,5 @@ +[저장] · [확정] · [초기화] · 떠남. + +- [저장] · [확정] = 한 본문 한 요청 · 서버가 차례(노선 → 설정 → 관 → 구조물 → 추가 측점 → 종단 → 관 옵션 → 상단측 → 횡단)로 · 하나라도 실패 = 되돌림. +- 횡단 패치 = [[crossPatchesFromCache]] · 노선 바뀐 [저장] = [[routeCarryInputs]] 를 같은 본문에. +- [폐기] = 세션 고침 칸만 지움 · 메모리는 떠남이 비움 · [초기화] = DB 노선 행 지우고 초기값으로. diff --git a/docs/guide/B05_B06/groups/screen.md b/docs/guide/B05_B06/groups/screen.md new file mode 100644 index 000000000..ab30a602b --- /dev/null +++ b/docs/guide/B05_B06/groups/screen.md @@ -0,0 +1,4 @@ +페이지 틀 · 보기 전환 · 패널 · 고름. + +- B05 ↔ B06 전환 = 보기 숨김 · 보임만 · 보인 보기가 가게 · 세션을 다시 읽음 · 묶음은 숨은 동안도 셈. +- 접기 = 그리기만 건너뜀 · 팝아웃 = 같은 함수 · 같은 가게. diff --git a/docs/guide/B05_B06/groups/server.md b/docs/guide/B05_B06/groups/server.md new file mode 100644 index 000000000..a47a6b96f --- /dev/null +++ b/docs/guide/B05_B06/groups/server.md @@ -0,0 +1,3 @@ +서버 창구 · 저장소 · 스키마 — 브라우저 요청을 받아 DB · 정본 파일을 읽고 씀. + +- 정본 쓰기 = [저장] · [확정] · [초기화] · 저장 밖 셋만. diff --git a/docs/guide/B05_B06/groups/stations.md b/docs/guide/B05_B06/groups/stations.md new file mode 100644 index 000000000..bdd9cd15d --- /dev/null +++ b/docs/guide/B05_B06/groups/stations.md @@ -0,0 +1,5 @@ +측점 목록 · 열쇠 · 갈아끼움 — 노선 · 시설이 바뀌면 측점을 다시 세움. + +- 열쇠 = [[common_util_station_key#stationKey]] 한 벌(파이썬 짝 · 거울 시험). +- 갈아끼움 [[swapSectionStations]] — 노선 초안 없는 차례 = 서버 정본 측점 기하 · 표본 유지(같은 자리 0.01 m · 축 1e-3). +- 지금 상세에 없던 측점이 정본 자리로 돌아옴(관 지움 ↶) = 정본 설계. diff --git a/docs/guide/B05_B06/groups/structures.md b/docs/guide/B05_B06/groups/structures.md new file mode 100644 index 000000000..697643744 --- /dev/null +++ b/docs/guide/B05_B06/groups/structures.md @@ -0,0 +1,5 @@ +구조물 — 추가 · 옮김 · 지움 · 형상 · 소단 · 시설 줄. + +- 주인 = 가게 `structures` · 화면 사본은 가게 알림으로 맞춤([[announceDesignStore]]). +- 시설 줄 측점 = 종류 표 · 구조물 · 관 알림이 다 온 뒤만 심음(모름 ≠ 빈 목록). +- 소단 사본 쓰는 자리 = 시설 줄 하나 · 같으면 안 씀. diff --git a/docs/guide/B05_B06/index.md b/docs/guide/B05_B06/index.md deleted file mode 100644 index 42e930e0f..000000000 --- a/docs/guide/B05_B06/index.md +++ /dev/null @@ -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 낡음 표시 | diff --git a/docs/guide/B05_B06/operations_b05.md b/docs/guide/B05_B06/operations_b05.md deleted file mode 100644 index fc3ee8706..000000000 --- a/docs/guide/B05_B06/operations_b05.md +++ /dev/null @@ -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` | diff --git a/docs/guide/B05_B06/operations_b06.md b/docs/guide/B05_B06/operations_b06.md deleted file mode 100644 index f617bd959..000000000 --- a/docs/guide/B05_B06/operations_b06.md +++ /dev/null @@ -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` | diff --git a/docs/guide/B05_B06/operations_common.md b/docs/guide/B05_B06/operations_common.md deleted file mode 100644 index 129643af5..000000000 --- a/docs/guide/B05_B06/operations_common.md +++ /dev/null @@ -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 분기). diff --git a/docs/guide/B05_B06/page.md b/docs/guide/B05_B06/page.md new file mode 100644 index 000000000..b64254087 --- /dev/null +++ b/docs/guide/B05_B06/page.md @@ -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」 · 서버 기본 표) — 사용자 협의 전 손대지 않음. +- 속도 목표는 기능 완성 뒤 따로 봄. diff --git a/docs/guide/B05_B06/rules.md b/docs/guide/B05_B06/rules.md deleted file mode 100644 index bb6cceb2a..000000000 --- a/docs/guide/B05_B06/rules.md +++ /dev/null @@ -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` | 사용자 협의 | diff --git a/docs/guide/B05_B06/session_keys.md b/docs/guide/B05_B06/session_keys.md deleted file mode 100644 index f014b2ba9..000000000 --- a/docs/guide/B05_B06/session_keys.md +++ /dev/null @@ -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` | 유토 판 · 표 · 횡단 판 자리 | diff --git a/docs/guide/B05_B06/values.md b/docs/guide/B05_B06/values.md deleted file mode 100644 index 2892738cc..000000000 --- a/docs/guide/B05_B06/values.md +++ /dev/null @@ -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()` · 같은 사진이면 걸음 아님 | diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 000000000..50891254a --- /dev/null +++ b/docs/mkdocs.yml @@ -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 diff --git a/docs/tools/README.md b/docs/tools/README.md new file mode 100644 index 000000000..08ffd2a00 --- /dev/null +++ b/docs/tools/README.md @@ -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` diff --git a/docs/tools/build.py b/docs/tools/build.py new file mode 100644 index 000000000..6b4c42839 --- /dev/null +++ b/docs/tools/build.py @@ -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']}", + "", + '
', + "", + *cards, + "
", + "", + ] + 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() diff --git a/docs/tools/group.py b/docs/tools/group.py new file mode 100644 index 000000000..b3a900cfe --- /dev/null +++ b/docs/tools/group.py @@ -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] diff --git a/docs/tools/groups.yml b/docs/tools/groups.yml new file mode 100644 index 000000000..9a8a4489e --- /dev/null +++ b/docs/tools/groups.yml @@ -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 diff --git a/docs/tools/nav_hook.py b/docs/tools/nav_hook.py new file mode 100644 index 000000000..ef8154ec2 --- /dev/null +++ b/docs/tools/nav_hook.py @@ -0,0 +1,11 @@ +"""mkdocs 훅 — build.py 가 쓴 왼쪽 메뉴(/.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 diff --git a/docs/tools/package-lock.json b/docs/tools/package-lock.json new file mode 100644 index 000000000..87aa3542a --- /dev/null +++ b/docs/tools/package-lock.json @@ -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" + } + } + } +} diff --git a/docs/tools/package.json b/docs/tools/package.json new file mode 100644 index 000000000..cae706d00 --- /dev/null +++ b/docs/tools/package.json @@ -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"}} \ No newline at end of file diff --git a/docs/tools/requirements.txt b/docs/tools/requirements.txt new file mode 100644 index 000000000..968820fc2 --- /dev/null +++ b/docs/tools/requirements.txt @@ -0,0 +1,4 @@ +mkdocs>=1.6 +mkdocs-material>=9.5 +mkdocstrings[python]>=0.26 +pyyaml>=6 diff --git a/resources/tester/fixtures/478_docs/api.json b/resources/tester/fixtures/478_docs/api.json new file mode 100644 index 000000000..8fbddb4dd --- /dev/null +++ b/resources/tester/fixtures/478_docs/api.json @@ -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" + ] + } +] diff --git a/resources/tester/fixtures/478_docs/functions.json b/resources/tester/fixtures/478_docs/functions.json new file mode 100644 index 000000000..3888393b9 --- /dev/null +++ b/resources/tester/fixtures/478_docs/functions.json @@ -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": [] + } +] diff --git a/resources/tester/fixtures/478_docs/registry.json b/resources/tester/fixtures/478_docs/registry.json new file mode 100644 index 000000000..44421aa1d --- /dev/null +++ b/resources/tester/fixtures/478_docs/registry.json @@ -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 + } +] diff --git a/resources/tester/fixtures/478_docs/session_keys.json b/resources/tester/fixtures/478_docs/session_keys.json new file mode 100644 index 000000000..8f133d7ac --- /dev/null +++ b/resources/tester/fixtures/478_docs/session_keys.json @@ -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" + ] + } +] diff --git a/resources/tester/test_478_docs.py b/resources/tester/test_478_docs.py new file mode 100644 index 000000000..208fe8d9d --- /dev/null +++ b/resources/tester/test_478_docs.py @@ -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)