fix(B08): 큰돌쌓기를 돌쌓기 식으로 돌리던 것 + 미확정 조건 셋 화면 노출

⚠ 마감 중 발견 — `boulder_masonry`(큰돌쌓기)를 `stone_masonry(dry)` 로 전개하고
있었음. 큰돌쌓기는 품셈 13-6, 돌쌓기는 13-4 로 **규격 축이 다름**: 돌쌓기는
뒷길이(35·45·55·60㎝), 큰돌쌓기는 직경(40~60·60~80·80~100㎝). 직경 60~80㎝
짜리가 「뒷길이 45㎝」 계수로 돌아 고임돌 0.15·야면석 0.88 이 붙고 있었음.
값이 나오기는 해서 어떤 시험도 안 잡던 자리 — 전개식이 설 때까지 미확보로
드러내고(`EXPANDER_WITHHELD`) 왜 안 두는지 사람이 읽게 적음.

⑱ 화면에 안 보이던 미확정 조건 셋을 구조물 원단위 탭에 띄움. 「무엇을 정해야
하는지」만으로는 부족하고 「정하면 얼마나 달라지는지」까지 적음.
- 흡출방지재·차수시트 — 보통인부 1.04 → 1.17 인/10㎡(약 +12.5 %), 근거
  13-6·13-7 [주]②. ⚠ 큰돌쌓기·큰돌붙이기에만 걸리고 돌쌓기(13-4)에는 괄호 값
  자체가 없다는 범위도 함께.
- 목재틀흙막이 원단위 — 1㎥당 건축목공 16.975인, 각재·판재가 없어 모자란 값.
- 내역서 수량 표시 자릿수 — `단수처리_규칙.md` 에 금액 자리만 있음.

검증 — 전체 612 passed, tsc 오류 0. 화면에서 셋 다 뜨는 것 확인.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 02:02:52 +09:00
co-authored by Claude Opus 5
parent a4c7c8c8cd
commit 4e1241a2cd
4 changed files with 74 additions and 1 deletions
@@ -72,6 +72,8 @@ class ObservedUnitTable:
entries: list[dict[str, Any]] = field(default_factory=list)
sources: dict[str, Any] = field(default_factory=dict)
not_found: dict[str, Any] = field(default_factory=dict)
#: 값을 바꾸는 **설계 조건**인데 우리 제원에 칸이 없는 것 — 화면이 보이게 한다.
pending_choices: dict[str, Any] = field(default_factory=dict)
def find(self, type_id: str, spec: dict[str, Any]) -> dict[str, Any] | None:
"""규격이 **모두** 맞는 줄만 돌려준다. 하나라도 어긋나면 없는 것으로 본다."""
@@ -101,6 +103,7 @@ def load_observed_table(path: Path | None = None) -> ObservedUnitTable:
entries=list(payload.get("entries") or []),
sources=payload.get("sources") or {},
not_found=payload.get("not_found") or {},
pending_choices=payload.get("pending_choices") or {},
)
@@ -288,7 +288,21 @@ OBSERVED_SPEC_KEYS: dict[str, tuple[str, ...]] = {
EXPANDERS = {
"masonry_wet": lambda h, l, o: stone_masonry(h, l, o, wet=True),
"masonry_dry": lambda h, l, o: stone_masonry(h, l, o, wet=False),
"boulder_masonry": lambda h, l, o: stone_masonry(h, l, o, wet=False),
# ⚠⚠ **큰돌쌓기(`boulder_masonry`)를 여기에 두지 않는다** (2026-09-07 발견).
# 큰돌쌓기는 품셈 **13-6** 이고 돌쌓기는 **13-4** 다 — **규격 축이 다르다.**
# 돌쌓기는 **뒷길이**(35·45·55·60㎝), 큰돌쌓기는 **직경**(40~60·60~80·80~100㎝).
# 앞서 `stone_masonry(dry)` 로 전개하고 있었는데, 그러면 직경 60~80㎝ 짜리가
# **「뒷길이 45㎝」 계수로 돌아 조용히 틀린 값**이 나온다(고임돌 0.15·야면석 0.88 …).
# ⚠ 값이 나오기는 하므로 어떤 시험도 안 잡던 자리다 — 「값이 있기는 하니 안 보이는」 그것.
# 전개식·관측 원단위가 설 때까지 **미확보로 드러낸다.**
}
#: 전개식을 일부러 안 두는 종류 — 왜 안 두는지 사람이 읽게 적는다.
EXPANDER_WITHHELD = {
"boulder_masonry": (
"큰돌쌓기는 품셈 13-6 이고 규격 축이 **직경**(40~60·60~80·80~100㎝)이다. "
"돌쌓기(13-4)의 **뒷길이** 계수로 돌리면 조용히 틀린 값이 나온다 — 전개식 미확보."
),
}
@@ -363,6 +377,11 @@ def expand(
end_m=end if structure.get("end_m") is not None else None,
options=dict(options),
)
withheld = EXPANDER_WITHHELD.get(type_id)
if withheld:
result.notes.append(f"전개식 미확보 — {withheld}")
return result
expander = EXPANDERS.get(type_id)
if expander is None:
# 전개식이 없으면 **관측 원단위표**를 본다(치수가 저장돼 있지 않은 종류).
@@ -456,6 +475,9 @@ def build_table(
"formwork_reuse_missing": formwork_missing,
# 동바리 — 대상이 없으면 0 이 아니라 「없음」이라고 말한다.
"shoring": shoring_status(),
# ⚠ 값을 바꾸는 설계 조건인데 우리 제원에 칸이 없는 것 — 화면에 드러낸다.
# 「무엇을 정해야 하는지」만으로는 부족하고 **「정하면 얼마나 달라지는지」**까지.
"pending_choices": (observed.pending_choices or {}).get("items") or [],
"totals": sorted(totals.values(), key=lambda entry: entry["name"]),
# 할증 전 값임을 응답에 못 박는다 — 자재총괄이 한 번만 붙인다(㉠).
"surcharge_applied": False,
@@ -102,6 +102,14 @@ export interface FormworkInfo {
formwork_notes?: string[];
formwork_reuse_missing?: string[];
shoring?: { applicable: boolean; reason: string; pending_types: string[] };
/** 값을 바꾸는 설계 조건인데 우리 제원에 칸이 없는 것 — 화면에 드러낸다. */
pending_choices?: {
label: string;
default?: unknown;
where?: string;
effect?: string;
scope?: string;
}[];
}
/** 성분이 어디로 가는지 — 화면에서도 보이게 한다. 규칙이 코드에만 있으면 잊힌다. */
@@ -349,6 +357,19 @@ export function renderUnitQuantityGrid(response: MaterialResponse): HTMLElement
wrap.append(line);
}
// ⚠ 값을 바꾸는 설계 조건인데 칸이 없는 것 — 「무엇을 정해야 하는지」만으로는 부족하고
// **「정하면 얼마나 달라지는지」**까지 보여야 사용자가 판단한다.
for (const choice of info.pending_choices ?? []) {
const line = document.createElement("p");
line.className = "b08-quantity__notice";
const parts = [`⚠ 미확정: ${choice.label}`];
if (choice.effect) parts.push(choice.effect.replace(/\*\*/g, ""));
if (choice.where) parts.push(`근거 ${choice.where}`);
if (choice.scope) parts.push(choice.scope.replace(/\*\*/g, ""));
line.textContent = parts.join(" · ");
wrap.append(line);
}
// ⚠ 품셈에 그 이름의 공종이 없어 **여러 공종으로 나뉘어 서는** 구조물 — 무엇으로
// 나뉘는지와 각 조각의 물량·갈래를 보인다. 코드만으로는 사람이 검증할 수 없다.
for (const group of response.composite ?? []) {
@@ -304,5 +304,32 @@
"source": "품셈 13-6-2 [주]⑩ 「큰돌쌓기(찰쌓기)의 뒤채움콘크리트량은 0.2㎥기준으로 하고 현지여건에 따라 0.3㎥까지 적용할 수 있다」"
}
]
},
"pending_choices": {
"note": "값을 바꾸는 **설계 조건**인데 우리 제원에 칸이 없는 것. 기본값과 「정하면 얼마나 달라지는지」를 함께 적어 화면이 보이게 한다.",
"items": [
{
"key": "anti_suction_sheet",
"label": "흡출방지재·차수시트 시공",
"default": false,
"where": "품셈 13-6·13-7 [주]② — 「흡출방지재 또는 차수시트를 시공하는 경우는 ( )의 값을 적용한다」",
"effect": "인부 수량이 갈림 — 큰돌쌓기 메쌓기 직경 40~60㎝ 기준 보통인부 1.04 → 1.17 인/10㎡ (약 +12.5 %)",
"scope": "⚠ 큰돌쌓기(13-6)·큰돌붙이기(13-7)에만 걸린다. 지금 쓰는 돌쌓기(13-4)에는 괄호 값 자체가 없다."
},
{
"key": "timber_crib_unit",
"label": "목재틀흙막이 원단위",
"default": "품셈 13-13-1 그대로",
"where": "품셈 13-13-1 (단위: 인/㎥당)",
"effect": "1㎥당 건축목공 16.975인 + 보통인부 1.848인. ⚠ 실무 감각에 맞는지 아무도 판단 못 했고, 각재·판재 자재가 카탈로그에 없어 **지금 값은 모자란 값**이다."
},
{
"key": "bill_quantity_digits",
"label": "내역서 수량 표시 자릿수",
"default": null,
"where": "`단수처리_규칙.md` 에 **금액 자리만 있고 수량 자리가 없음**",
"effect": "표시 자릿수와 계산 자릿수가 다르면 보는 사람이 반올림해 곱해 보고 「틀렸다」고 한다(2026-09-07 실제로 그렇게 오진한 일이 있었다)."
}
]
}
}