Files
Aislo/B08_Quantity/B08_Quantity_Provenance.py
T
eomsangdonandClaude Opus 5 6e8de27966 feat(B08): 토적표 근거 호버와 출처 등급 분류 — 개발환경 전용
화면에 뜬 숫자가 어디서 와서 어떻게 나왔는지 가릴 길이 없어 개발·검산이 막히던 것을
엶(PLAN 8-36 ①②③④). 칸에 마우스를 올리면 등급·값·식·원천·자리가 카드로 뜨고,
토글을 켜면 등급색이 칸 왼쪽 띠로 붙음.

- `common_util/common_util_provenance.py` — 등급 상수 여덟과 `ColumnProvenance`.
  사람이 고르는 여섯(입력·측량·기준·계산·최종·막힘)에 `excluded`(일부러 안 셈,
  채우면 이중계상)와 `unclassified`(판단 미룸)를 더함. `excluded` 는 데스크탑 보조의
  B09 조사에서 나온 것으로 `blocked` 와 뜻이 정반대라 갈라 둠. 값 안에 선택이 숨은
  열을 위해 `rule`(채택 규칙) 칸도 둠.
- ⚠ 로직 보안 — `provenance_payload()` 가 개발환경이 아니면 `None` 을 돌려주고
  라우터가 응답에서 칸을 통째로 뺌. 화면에서 숨기는 것이 아니라 안 보내는 것임.
  문의 정본은 `is_dev_environment()` 하나로 통일.
- `ui_template/ui_template_provenance.ts` — 호버 카드·등급색·토글 한 벌. B09 도 같이 씀.
- `B08_Quantity/B08_Quantity_Provenance.py` — 토적표 20열 사전. 사전은 **열 단위**이고
  줄마다 갈리는 사유만 줄 쪽에서 얹음(칸마다 지으면 한 장이 6천 칸이라 응답이 붐).
- 토적표에는 `final` 열이 하나도 없어 억지로 붙이지 않음 — 중간 장부이고 내역서로
  나가는 값은 토공집계표에서 섬.

자체검증 — 시험 8개 추가(`test_b08_provenance.py`). 값어치는 첫 번째에 있음:
사전 열 이름이 전부 실제 `EarthworkRow` 에 있는가(엔진이 이름을 갈면 사전만 옛것으로
남는데 화면에서는 카드가 그냥 안 떠 눈에 안 띔). 배포환경 `None` 도 시험으로 박음.
브라우저(ORCA 5173, 실제 사전을 물려 토적표를 그림) — 표시된 칸 40개(2줄×20열) ·
등급 갈래 survey 12 / calc 28 · 카드에 「절토 토사 보정량 · 계산 · 값 45.00 ·
식 절토 토사 입적 × 토량환산계수(다짐) · 자리 EarthworkTable.py:210」 · 토글에 배경이
투명 → 초록 7 % 로 바뀌고 왼쪽 띠 inset 3px. 줄 사유가 그 줄 모든 칸에 뜨던 것을
사유가 닿는 열(`ditch_*`)에만 뜨게 고침 — 「절토 보정량」 카드에 「측구 가름값이…」 가
떠서 읽는 사람을 속이던 자리임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RANEBHns1S4tkmsYwewtk
2026-09-12 15:16:12 +09:00

164 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""B08 수량 화면의 **근거 사전** — 어느 숫자가 어디서 와서 어떻게 나왔나 (PLAN 8-36 ④).
⚠⚠ **개발 전용.** 사전은 `provenance_payload()` 를 거쳐 나가고, 개발환경이 아니면 `None`
이라 응답에 칸 자체가 안 생긴다. 화면에서 숨기는 것이 아니라 **안 보내는 것**이다.
왜 이 파일인가
「식」과 「원천」의 정답은 값을 낳는 엔진이 안다. 화면 TS 에 손으로 적어 두면 엔진을
고칠 때 설명만 옛것으로 남는다. 엔진 옆(같은 폴더)에 두어 같이 눈에 들어오게 한다.
⚠ **열 단위로 적는다.** 토적표 한 장이 30열 × 200줄 = 6천 칸이라 칸마다 지으면 응답이
붐는다. 줄마다 갈리는 것(측구 안분 폴백 사유 등)은 줄이 이미 `notes` 로 들고 있고,
화면이 그것을 카드에 덧붙인다.
⚠ **토적표에는 `final`(최종) 열이 없다 — 억지로 붙이지 않았다.**
이 표는 중간 장부다. 내역서로 나가는 값은 **토공집계표**에서 선다. 여섯 등급을
한 장에 다 채우려고 아무 열에나 `final` 을 붙이면 「분류가 있다」는 거짓만 남는다.
이 어긋남은 등급을 고칠 근거이므로 PLAN 8-36 ① 에 그대로 남긴다.
"""
from __future__ import annotations
from typing import Any
from common_util.common_util_provenance import (
TIER_CALC,
TIER_SURVEY,
ColumnProvenance,
provenance_payload,
sheet_provenance,
)
#: 토량환산계수가 어디서 오는지 — 여러 열이 같은 문장을 쓰므로 한 벌로 둔다.
_FACTOR_SOURCE = (
"토량환산계수(다짐) — 기본값 `config_system_design.EARTHWORK_CONVERSION_FACTORS`, "
"프로젝트가 고른 값이 있으면 산출 조건 패널의 값"
)
#: 단면적 넷의 공통 원천. B06 이 낸 설계 단면을 **그대로** 읽는다(여기서 다시 안 짓는다).
_SECTION_SOURCE = "B06 횡단 설계가 낸 측점별 단면적"
#: 평균단면적법 한 줄. 신규 문서 5장 「다. 공사수량의 산출」.
_MEAN_AREA = "(앞 측점 단면적 + 이 측점 단면적) ÷ 2 × 두 측점 사이 거리"
def _area(key: str, label: str, extra: str = "") -> ColumnProvenance:
"""단면적 열 — B06 설계값을 그대로 옮긴 자리라 식이 없다."""
return ColumnProvenance(
key=key,
label=label,
tier=TIER_SURVEY,
formula="설계가 낸 값을 그대로 읽음 (여기서 다시 계산하지 않음)",
source=_SECTION_SOURCE + (f" · {extra}" if extra else ""),
code="B08_Quantity_Engine_EarthworkTable.py:StationArea.from_design",
)
def _volume(key: str, label: str, area_label: str) -> ColumnProvenance:
return ColumnProvenance(
key=key,
label=label,
tier=TIER_CALC,
formula=_MEAN_AREA.replace("단면적", area_label),
source="첫 측점은 앞이 없어 비어 있음 (실무 토적표도 첫 줄 체적이 빈칸)",
code="B08_Quantity_Engine_EarthworkTable.py:200 mean_volume",
)
def _adjusted(key: str, label: str, volume_label: str) -> ColumnProvenance:
return ColumnProvenance(
key=key,
label=label,
tier=TIER_CALC,
formula=f"{volume_label} × 토량환산계수(다짐)",
source=_FACTOR_SOURCE,
code="B08_Quantity_Engine_EarthworkTable.py:210",
)
def earthwork_sheet() -> dict[str, Any]:
"""토적표 한 장의 사전. 열 키는 화면 `EarthworkRow` 와 같은 낱말이라야 한다."""
return sheet_provenance(
[
ColumnProvenance(
key="chainage_m",
label="측점",
tier=TIER_SURVEY,
formula="노선 시점에서 잰 이정(m). 화면은 NO.n+m 으로 적음",
source="B05 종단이 놓은 측점 배치",
code="B08_Quantity_Engine_EarthworkTable.py:StationArea.chainage_m",
),
ColumnProvenance(
key="distance_m",
label="거리",
tier=TIER_CALC,
formula="이 측점 이정 − 앞 측점 이정",
source="B05 종단 측점 배치. 첫 줄은 앞이 없어 0",
code="B08_Quantity_Engine_EarthworkTable.py:195",
),
_area("cut_soil_area_m2", "절토 토사 단면적"),
_volume("cut_soil_volume_m3", "절토 토사 입적", "절토 토사 단면적"),
_adjusted("cut_soil_adjusted_m3", "절토 토사 보정량", "절토 토사 입적"),
_area("cut_rock_area_m2", "절토 암석 단면적", "암 갈래는 측점의 `cut_rock_kind`"),
_volume("cut_rock_volume_m3", "절토 암석 입적", "절토 암석 단면적"),
_adjusted("cut_rock_adjusted_m3", "절토 암석 보정량", "절토 암석 입적"),
_area(
"ditch_soil_area_m2",
"측구터파기 토사 단면적",
"지반 유형·암반 경계선으로 B06 이 가른 값. 가름이 없는 옛 저장분만 "
"절토 토사:암 면적비로 안분하고 그 줄에 사유가 남음",
),
_volume("ditch_soil_volume_m3", "측구터파기 토사 입적", "측구 토사 단면적"),
_adjusted("ditch_soil_adjusted_m3", "측구터파기 토사 보정량", "측구 토사 입적"),
_area(
"ditch_rock_area_m2",
"측구터파기 암석 단면적",
"위와 같은 가름값. 0.0 은 설계가 낸 「없음」이고 값 없음과 다름",
),
_volume("ditch_rock_volume_m3", "측구터파기 암석 입적", "측구 암석 단면적"),
_adjusted("ditch_rock_adjusted_m3", "측구터파기 암석 보정량", "측구 암석 입적"),
ColumnProvenance(
key="adjusted_total_m3",
label="보정량계",
tier=TIER_CALC,
formula="절토 토사 보정량 + 절토 암석 보정량 + 측구 토사 보정량 + 측구 암석 보정량",
source="네 보정량의 합. 성토에 쓸 수 있는 양으로 환산한 뒤의 값",
code="B08_Quantity_Engine_EarthworkTable.py:215",
),
_area("fill_area_m2", "성토 단면적"),
_volume("fill_volume_m3", "성토 입적", "성토 단면적"),
ColumnProvenance(
key="diverted_m3",
label="유용토",
tier=TIER_CALC,
formula="min(보정량계, 성토 입적)",
source="그 측점에서 절취분과 성토분이 서로 만나는 몫",
code="B08_Quantity_Engine_EarthworkTable.py:222",
),
ColumnProvenance(
key="balance_m3",
label="차인토량",
tier=TIER_CALC,
formula="보정량계 성토 입적",
source="양수면 남는 흙(사토), 음수면 모자란 흙(객토)",
code="B08_Quantity_Engine_EarthworkTable.py:223",
),
ColumnProvenance(
key="cumulative_m3",
label="누가토량",
tier=TIER_CALC,
formula="첫 줄부터 이 줄까지 차인토량을 더해 온 값",
source="유토곡선(mass haul)의 세로축이 되는 값",
code="B08_Quantity_Engine_EarthworkTable.py:225",
),
]
)
def quantity_provenance() -> dict[str, Any] | None:
"""B08 응답에 실을 사전 — **개발환경이 아니면 `None`.**
시트를 늘릴 때는 여기 한 줄만 더한다. 화면은 시트 이름으로 찾아 쓴다.
"""
return provenance_payload({"earthwork": earthwork_sheet()})