feat(b06): 횡단도 벽 전면 기울기도 품셈 13-4-4 [주]⑪ 표준경사 표 — 관 유입·유출·독립 기슭막이·다단·C군 벽 · TS 짝(common_util_masonry_slope) 거울 시험 · 번들이 표 JSON 도 봄(B5)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
2026-09-14 13:14:20 +09:00
co-authored by Claude Opus 5
parent e8cce87413
commit 0e609e69a6
18 changed files with 401 additions and 38 deletions
+4
View File
@@ -177,6 +177,10 @@ function revetSide(values: Record<string, unknown>, role: string): CulvertSetSpe
revet_before_m: num(values[`${role}_revet_before_m`], num(values.before_m, null)),
revet_after_m: num(values[`${role}_revet_after_m`], num(values.after_m, null)),
face_slope: REVET_FACE_SLOPE,
// 전면 기울기를 B08 과 같은 칸으로 가르는 값 — 저장 원본 그대로(파이썬 `_revet_side`).
face_side: values.side ?? null,
face_role: values.face_role ?? null,
face_slope_ratio: values.face_slope_ratio ?? null,
};
if (height !== null && height > 0) {
spec.apron_length_m = round(height * APRON_LENGTH_FACTOR, 3);
+88
View File
@@ -0,0 +1,88 @@
/* =============================================================================
* common_util_masonry_slope.ts
* 벽 전면 기울기(1:n 의 n) — 품셈 13-4-4 [주]⑪ 표준경사 표(원문 L7185~7191)를 읽음.
*
* ⚠ **파이썬과 짝**(CLAUDE.md 5장 ①) — `B08_Quantity_Engine_UnitQuantity_StoneSpec.face_slope_ratio`
* + `common_util_structure_face_role.structure_face_role_of`. 거울 시험
* `resources/tester/test_b06_wall_lean_table.py` 가 네 갈래(직고 × 성토/절토 × 메/찰 + 사용자 칸)를
* 한 칸씩 대조. 한쪽만 고치면 횡단도 벽과 B08 수량이 갈림(2026-09-14 브레인 판정 「양쪽 다 이 표를」).
* ⚠ 표는 부르는 쪽이 넘김 — 이 파일은 아무것도 import 하지 않음(거울 시험이 단독으로 돌림).
* ========================================================================== */
/** 표준경사 표 — `resources/data_masonry/masonry_slope_*.json` 의 모양. */
export interface MasonrySlopeTable {
steps_m?: number[];
table?: Record<string, Record<string, number[]>>;
}
/** 벽 한 매의 기울기 입력 — B08 이 그 벽을 셀 때 보는 칸과 같음. */
export interface WallLeanInput {
/** 형태 — 「돌쌓기(찰)」·「돌쌓기(메)」만 표를 읽음(나머지는 종전 0.3). */
form?: string | null;
/** 직고(m) — 순수 높이. */
height_m: number;
/** 측점 단면유형 `design.section_mode`. */
section_mode?: string | null;
/** 설치 측 — 「자동(성토 쪽)」·「좌」·「우」(없으면 자동). */
side?: string | null;
/** 성토/절토 칸 — 고르면 이김. */
face_role?: string | null;
/** 전면 기울기 칸 — 수(>0)면 이김(파이썬 `_num` 과 같이 글은 안 봄). */
face_slope_ratio?: number | string | null;
}
/** 표를 못 고를 때의 종전값 — 파이썬 `LEGACY_FACE_SLOPE_RATIO`. */
export const LEGACY_LEAN_RATIO = 0.3;
const FILL = "성토";
const CUT = "절토";
/** 형태 → 찰(true)/메(false) — 파이썬 `REVETMENT_STONE_FORMS`. */
const STONE_FORMS: Record<string, boolean> = { "돌쌓기(찰)": true, "돌쌓기(메)": false };
/** 단면유형 → (좌측 역할, 우측 역할). 좌 = +offset. */
const MODE_ROLES: Record<string, [string, string]> = {
left_cut: [CUT, FILL],
right_cut: [FILL, CUT],
both_cut: [CUT, CUT],
both_fill: [FILL, FILL],
};
const LEFT = new Set(["좌", "left", "L"]);
const RIGHT = new Set(["우", "right", "R"]);
const AUTO = new Set(["자동(성토 쪽)", "자동", "auto", ""]);
/** 성토/절토 — 칸이 이기고, 비면 단면유형 + 설치 측. 못 가르면 `null`(성토로 눅이지 않음). */
export function faceRoleOf(
sectionMode: string | null | undefined,
side: string | null | undefined,
faceRole?: string | null,
): string | null {
const picked = String(faceRole ?? "").trim();
if (picked === FILL || picked === CUT) return picked;
const roles = MODE_ROLES[String(sectionMode ?? "").trim()];
if (!roles) return null;
const where = String(side ?? "").trim();
if (LEFT.has(where)) return roles[0];
if (RIGHT.has(where)) return roles[1];
if (AUTO.has(where)) return roles[0] === CUT && roles[1] === CUT ? null : FILL;
return null;
}
/** 벽 전면 기울기 n — 표를 못 고르면 종전 0.3. */
export function wallLeanRatio(table: MasonrySlopeTable, input: WallLeanInput): number {
const wet = STONE_FORMS[String(input.form ?? "").trim()];
if (wet === undefined) return LEGACY_LEAN_RATIO;
const given = input.face_slope_ratio;
if (typeof given === "number" && Number.isFinite(given) && given > 0) return given;
const face = faceRoleOf(input.section_mode, input.side, input.face_role);
if (!face) return LEGACY_LEAN_RATIO;
const row = table.table?.[wet ? "찰쌓기" : "메쌓기"]?.[face];
if (!row?.length) return LEGACY_LEAN_RATIO;
const steps = table.steps_m ?? [];
let index = steps.length;
for (let i = 0; i < steps.length; i += 1) {
if (input.height_m <= steps[i]) {
index = i;
break;
}
}
return row[Math.min(index, row.length - 1)];
}
+6
View File
@@ -21,6 +21,8 @@ logger = logging.getLogger(__name__)
ROOT = Path(__file__).resolve().parents[1]
# 번들이 낡았는지 재는 대상 — 기하 계통이 걸쳐 있는 폴더 + 구조물도 식 풀이(B08, 2026-09-13).
SOURCE_DIRS = ("B05_Profile", "B06_Section", "B08_Quantity", "common_util")
# 번들에 박히는 자료(JSON) — 벽 전면 기울기 표준경사 표(2026-09-14 B5). 표만 바뀌어도 다시 빌드.
SOURCE_DATA_DIRS = ("resources/data_masonry",)
# 번들 만들기·실행 상한(초). 실측 번들 실행 0.1초, 빌드 3초 수준이라 넉넉하다.
BUILD_TIMEOUT_S = 300
RUN_TIMEOUT_S = 600
@@ -48,6 +50,10 @@ def bundle_stale(bundle: Path) -> bool:
for path in (ROOT / directory).rglob("*.ts"):
if path.stat().st_mtime > built_at:
return True
for directory in SOURCE_DATA_DIRS:
for path in (ROOT / directory).glob("*.json"):
if path.stat().st_mtime > built_at:
return True
return False
@@ -37,6 +37,9 @@ export interface WallSpec {
tiers: number | null;
lift_m: number | null;
shift_m: number | null;
/** 전면 기울기 판정 칸 — 성토/절토 · 전면 기울기(저장 원본 · B08 과 같은 칸, 2026-09-14 B5). */
face_role: string | null;
face_slope_ratio: number | string | null;
}
/** 구간 경계 측점을 구간 안으로 볼 허용 오차(m) — 파이썬 `_EDGE_TOLERANCE_M`. */
@@ -95,6 +98,8 @@ export function wallSpecsFrom(
tiers: num(options.tiers),
lift_m: num(options.lift_m),
shift_m: num(options.shift_m),
face_role: (options.face_role as string) ?? null,
face_slope_ratio: (options.face_slope_ratio as number | string) ?? null,
});
}
return specs;