Files
Aislo/B06_wf3_ProfileCross/B06_wf3_ProfileCross_Schema.py
T
eomsangdonandClaude Opus 5 f35ca7e1c9 feat(B06): 유토곡선(Mass Haul Diagram) 패널 — 종단면도와 한 슬라이드 패널로 결합
참고 도면 `13 유토곡선(울진 울진 대흥 산65 외2(3공구)).pdf` 분석 결과를 반영해
B06 종단면도 바로 아래에 유토곡선을 추가하고, 둘을 접이식 상단 슬라이드 패널
하나로 묶었다.

계산 (프론트 전담, 확정 시에만 영구 저장)
- `_UI_MassHaul.ts` 신설: 평균단면법으로 구간 토량을 만들고 토량환산계수로 기준을
  통일해 누가토량을 누적하는 순수 함수. DOM 의존 0이라 B08/B09가 재사용 가능.
- 기준상태는 다짐상태 — 운반거리 산정 시 모든 수량은 다짐상태로 환산하고 내역서
  수량은 자연상태로 한다(국도건설공사 설계실무 요령). 내역서용 자연상태 수량은
  cut_natural_m3로 함께 보존한다.
- 지반유형이 다른 구간은 각 측점이 자기 절반(단면적 × Δd / 2)을 자기 지반유형으로
  가져간다. 두 절반의 합이 평균단면법과 정확히 일치한다.
- 측점 조작마다 백엔드를 왕복하지 않고 메모리에서 계산하며, 확정 시점에만
  longitudinal_sections.data.mass_haul에 저장한다.

X축 정렬
- longitudinalMaxChainage()를 공용으로 신설해 종단 렌더러와 유토곡선이 같은 X
  매핑을 쓰게 했다. 두 SVG는 chart-wrap 하나를 공유해 가로 스크롤도 함께 움직인다.

패널 높이
- 아래 경계 리사이저로 사용자가 조절한다. 기본값 이상으로 키우면 종단도는 220px을
  지키고 늘어난 몫은 유토곡선이 먹으며, 기본값보다 줄이면 둘이 같은 비율로 작아진다.
- 종단도가 줄면 Y스케일도 그 높이로 다시 잡아 표고가 잘리지 않게 했다.

토량환산계수
- config_system.py 5-4-3절 신설(유일한 정의처). 표준품셈 암종별 범위를 B06 지반유형
  3종에 대응시킨 제안값이며 현장 조정이 전제라 env 없이 상수로 두었다.

검증: tsc 0건, ruff 통과, prettier 적용, 전 파일 700줄 이하.
계산 엔진 테스트 10종 전건 통과(평균단면법·다짐환산·지반유형 배분·비정규 측점
간격·정렬·중복 측점·계수 부재 폴백·페이로드 반올림). 서버 기동 및 번들 포함 확인.

Phase 2(평형선·운반토량 Q·평균운반거리 L·장비 배분·M.N)는 장비 거리 경계와 M.N
정의가 미확정이라 제외했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 09:02:52 +09:00

154 lines
5.7 KiB
Python

"""B06 종횡단 조회·확정 응답 검증 모델."""
from typing import Any, Literal
from pydantic import BaseModel, Field
class SectionRegenerateRequest(BaseModel):
"""표시 옵션의 횡단 반폭 변경에 따른 종횡단 재생성 요청."""
cross_half_width_m: float = Field(..., gt=0)
class CompanyStandardProject(BaseModel):
"""같은 회사의 표준횡단 설정값 보유 프로젝트 항목(조회용)."""
project_id: str
name: str
class CompanyStandardListResponse(BaseModel):
"""같은 회사에서 설계값을 불러올 수 있는 프로젝트 목록."""
status: str = "success"
projects: list[CompanyStandardProject]
class CompanyStandardResponse(BaseModel):
"""특정 프로젝트의 표준횡단 설정값(미리보기·적용용, 회사 스코프)."""
status: str = "success"
project_id: str
standard_cross_section: dict[str, Any]
class CrossDesignRequest(BaseModel):
"""측점 하나의 표준횡단 설계(지반유형·단면유형) 지정 요청.
버튼 클릭 즉시 절·성토 단면적을 계산해 잠정치로 저장한다. 편절편성은 절토측이
측구측이라 ditch_side가 무시되고, 양절·양성에서만 배수 방향으로 사용된다.
"""
chainage_m: float = Field(..., ge=0)
ground_type: Literal["soil", "ripping_rock", "blasting_rock"]
section_mode: Literal["left_cut", "right_cut", "both_cut", "both_fill"]
ditch_side: Literal["left", "right"] | None = None
# 측구 형식: 일반(사다리꼴)/L형. L형은 암 구간에서만 허용된다.
ditch_type: Literal["standard", "l_type"] = "standard"
# 포장 중첩 여부 — 횡단경사·포장층만 포장 그룹 값으로 계산한다.
paved: bool = False
# 암 경계선 오프셋(m, 지면선 기준 하향 음수). 암 지반 2단계 절토 무릎 계산에 쓴다.
rock_boundary_offset_m: float | None = None
# 암 지반 2단계 경사(암반 경계 아래=암 경사, 위=토사 경사) 적용 여부. 토글로 해제 가능.
two_stage_slope: bool = True
# 측구 생성 여부. None=자동 판정(측구측 절토면만 생성), True/False=수동 override.
ditch_enabled: bool | None = None
# B06 설정 패널 편집값(STANDARD_CROSS_SECTION 형태). 요청값 → config 기본값 순.
standard_cross_section: dict[str, Any] | None = None
class CrossDesignResponse(BaseModel):
"""측점 표준횡단 설계 계산 결과(잠정치)."""
status: str = "success"
chainage_m: float
design: dict[str, Any]
class CrossSectionPatch(BaseModel):
"""확정 시 측점별 data.design에 병합할 프론트 세션 보관값."""
chainage_m: float = Field(..., ge=0)
# 암 경계선 오프셋(m, 지면선 기준 하향 음수 — 계획선 아님). 암 지반 측점만 의미 있다.
rock_boundary_offset_m: float | None = None
class SectionConfirmRequest(BaseModel):
"""종횡단 확정 요청. 확정 시 저장할 표준 횡단면 설정값(선택)을 함께 받는다."""
# B06 표준 횡단면 설정 패널 편집값(토사/암/포장). longitudinal_sections.data.options에 저장.
standard_cross_section: dict[str, Any] | None = None
# 측점별 세션 보관값(암 경계선 오프셋 등) — 확정 시점에 일괄 DB 병합.
cross_patches: list[CrossSectionPatch] | None = None
# 프론트가 계산한 유토곡선 결과. longitudinal_sections.data.mass_haul에 저장한다.
# 계산은 프론트 전담(네트워크 왕복 회피)이라 백엔드는 검산 없이 스냅샷으로 보관한다.
mass_haul: dict[str, Any] | None = None
class SectionConfirmResponse(BaseModel):
"""종횡단 확정 결과."""
status: str = "success"
project_id: str
route_id: int
confirmed: bool = True
class SectionOptionDefaults(BaseModel):
"""config에서 읽은 종횡단 생성 기본 옵션."""
station_interval_m: float
cross_half_width_m: float
cross_sample_interval_m: float
long_sample_interval_m: float
vertical_exaggeration: float
class EarthworkConversionFactor(BaseModel):
"""지반유형 하나의 토량환산계수(둘 다 자연상태 기준 배율).
loose = 흐트러진 상태 / 자연상태(팽창률), compacted = 다져진 상태 / 자연상태(다짐률).
정의처는 config.config_system.EARTHWORK_CONVERSION_FACTORS 한 곳뿐이다.
"""
loose: float = Field(..., gt=0)
compacted: float = Field(..., gt=0)
class SectionContextResponse(BaseModel):
"""B06 진입 시 필요한 확정 경로 컨텍스트와 기본 옵션."""
project_id: str
route_id: int | None = None
filter_key: str | None = None
method: str | None = None
smooth: bool | None = None
crs_epsg: int | None = None
defaults: SectionOptionDefaults
# 표준 횡단면 설정 패널(토사/암/포장) 기본값. config STANDARD_CROSS_SECTION 사본.
standard_cross_section: dict[str, Any] = Field(default_factory=dict)
# 암 경계선 기본 오프셋(m)과 상/하 제어 스텝(m).
rock_boundary_default_offset_m: float = -0.5
rock_boundary_step_m: float = 0.1
# 지반유형별 토량환산계수. 유토곡선 계산은 프론트가 이 값으로 수행한다.
earthwork_conversion: dict[str, EarthworkConversionFactor] = Field(default_factory=dict)
class SectionSummaryResponse(BaseModel):
"""종횡단 요약 조회 결과."""
status: str = "success"
project_id: str
route_id: int
longitudinal: dict[str, Any] | None = None
length_m: float | None = None
cross_section_count: int = 0
class SectionDetailResponse(BaseModel):
"""종단·횡단 SVG 렌더링에 필요한 원시 샘플 데이터."""
longitudinal: dict[str, Any]
cross_sections: list[dict[str, Any]]