Files
Aislo/B06_wf3_ProfileCross/B06_wf3_ProfileCross_Schema.py
T
eomsangdonandClaude Opus 5 b0fe443269 feat(B06): 자연방토 반영 — 사토를 자연방토/사토장 운반으로 가름
판정 기준은 성토측 자연 지반 경사(사용자 확정). 지반이 임계보다 가파르면 부어 놓은 흙이
쌓이지 않고 스스로 흘러내리므로 운반비를 세지 않는다.

계상 방식은 참고 도면을 근거로 정했다. 도면의 사토 balloon은 L=(운반거리) 없이
M.N=측점만 적는다 - 그 노선의 사토가 이미 운반비 없이 처리되고 있다는 뜻이고,
자연방토라는 이름을 안 썼을 뿐 처리 방식이 자연방토다. 그래서 새 항목을 만들지 않고
사토 안에서 가른다. 판정 통과분은 도면과 똑같이 M.N만, 실패분에만 "사토장=X㎥"를 더한다.
수량은 그대로 두고 성토로 흡수하지 않아 유토곡선 모양과 누가토량은 안 바뀐다.

- config NATURAL_SPOIL_MIN_GROUND_SLOPE = 1/1.5 (rise/run, 약 33.7도) 신설.
  유일한 정의처이며 프론트는 context로 받아 쓴다. 못 받으면 자연방토 없음(보수적)
- 엔진 fill_ground_slope(): 노면 끝~성토 사면의 첫 지반 교차까지 평균 경사.
  양쪽이 다 성토면 완만한 쪽을 택한다(보수적)
- AreaSample/MassHaulPoint에 natural_spoil - 양 끝 측점이 둘 다 가능할 때만 참
- HaulResidual.natural_m3 - 잔량 구간에서 자연방토 가능한 길이 비율만큼 계상.
  장거리 상쇄로 깎이면 안분·자연방토도 같은 비율로 감소
- HaulPlan.natural_spoil_m3 + 요약 칩

700줄 초과 2건 분리: _Engine_Areas.py(면적 적분), _UI_MassHaul_Settle.ts(안분·장비·상쇄·
직렬화). 의존은 한 방향(Balance -> Settle 함수, Settle -> Balance 타입만)이라 순환 없음.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 18:18:08 +09:00

174 lines
7.0 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 HaulEquipmentLimit(BaseModel):
"""평균운반거리 상한 하나(m). max_distance_m=None이면 상한 없음(나머지를 전부 받는다).
정의처는 config.config_system.EARTHWORK_HAUL_EQUIPMENT_LIMITS_M 한 곳뿐이다.
프론트(유토곡선 토량 분배)는 이 값을 받아 쓰고 사본을 두지 않는다.
"""
key: str
max_distance_m: float | None = None
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)
# 평균운반거리별 운반장비 경계. 유토곡선의 토량 분배가 이 값으로 장비를 고른다.
haul_equipment_limits: list[HaulEquipmentLimit] = Field(default_factory=list)
# 자연방토 판정 경사(rise/run). 성토측 자연 지반이 이보다 가파르면 흙이 스스로 흘러내려
# 운반비를 세지 않는다. 정의처는 config_system.NATURAL_SPOIL_MIN_GROUND_SLOPE 한 곳뿐이다.
natural_spoil_min_ground_slope: float | None = None
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]]
# 유토곡선 balloon을 사용자가 끌어 옮긴 위치(띠 번호 -> [dx, dy], px).
# longitudinal_sections.data.mass_haul.balloon_offsets에 확정 시점에 저장된 값이며,
# 브라우저가 바뀌어도 같은 자리에 뜨도록 진입 시 프론트 캐시의 씨앗으로 내려보낸다.
balloon_offsets: dict[str, list[float]] | None = None