fix(B08): 갈래 키를 조립하지 않고 원본값으로 보냄 + 특수문자 깃발
⚠ 계약 변경(2026-09-07 3자). 품셈 원문이 규격 표기를 한 종류로 안 씀 — **물결표만 셋**(`∼` U+223C 662회 · `~` U+FF5E 459회 · `~` U+007E 2회), 곱셈표도 둘(`×` 97회 · `x` 4회). 같은 절 안에서도 갈림(13-06-01·02 는 `∼`, 13-06-03 은 `~`). **두 창이 각자 키를 조립하면 글자 하나로 영영 안 맞음.** - `#55cm이하`·`#직경60㎝이상∼80㎝미만` 같은 키를 **우리가 만들지 않음**. `variant_axis`(어느 축) + `variant_value`(저장 원본값 그대로)만 보내고, 원문을 읽는 쪽이 표기를 흡수함. 「…㎝ 이하」 구간 나누기도 그쪽 몫 — 그 구간이 품셈 표의 구조이기 때문. - ⚠ **메/찰은 예외** — 키 표기가 아니라 **공종 자체가 갈리는 의미 판정**이라 코드가 `FP-13-06-01`/`-02` 로 통째로 달라짐. 그것은 우리 몫으로 유지. - 계약 시험에 못 박음 — 코드에 `#`·`㎝`·`cm` 가 들어가면 깨짐. 원본값을 가공해도 깨짐(물결표 종류를 바꾸거나 단위를 붙이면 안 됨). - `data_masonry` 의 갈래표는 지우지 않고 **참고**로 남김 — 서브 판정과 어긋나면 그것이 곧 신호. 마스터에 `special_glyphs` 깃발 — 규격 표기에 쓰인 특수문자 종류. 값은 안 고치고 표시만 함. 물결표가 두 종류 섞인 표가 실제로 5개 있는 것을 시험으로 못 박음 (계약 근거가 사라지면 깨짐). 검증 — 전체 653 passed, tsc 오류 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -407,6 +407,29 @@ def formula_rows(table: dict[str, Any]) -> list[str]:
|
||||
return found
|
||||
|
||||
|
||||
# ⚠ **규격 표기에 쓰이는 특수문자** — 원문이 한 종류로 안 쓴다(2026-09-07 실측).
|
||||
# 물결표만 셋이다: `∼`(U+223C) 662회 · `~`(U+FF5E) 459회 · `~`(U+007E) 2회.
|
||||
# 곱셈표도 `×`(U+00D7) 97회 · `x`(U+0078) 4회로 갈린다.
|
||||
# **두 창이 각자 갈래 키를 조립하면 글자 하나로 영영 안 맞는다** — 그래서 우리는
|
||||
# 키를 조립하지 않고 **저장 원본값만** 보낸다(인계 계약). 여기서는 **표시만** 한다.
|
||||
# ⚠ 값을 고치지 않는다 — 원문 표기를 흡수하는 것은 **원문을 읽는 쪽**의 몫이다.
|
||||
SPECIAL_GLYPHS = {
|
||||
"∼": "∼(U+223C)",
|
||||
"~": "~(U+FF5E)",
|
||||
"~": "~(U+007E)",
|
||||
"×": "×(U+00D7)",
|
||||
"x": "x(U+0078)",
|
||||
"–": "–(U+2013)",
|
||||
"‐": "‐(U+2010)",
|
||||
}
|
||||
|
||||
|
||||
def special_glyphs(table: dict[str, Any]) -> list[str]:
|
||||
"""규격 표기에 쓰인 특수문자 종류. 갈래 키를 맞출 때 대조할 자리."""
|
||||
text = " ".join(norm(cell) for row in table.get("rows", []) for cell in row)
|
||||
return sorted({SPECIAL_GLYPHS[ch] for ch in text if ch in SPECIAL_GLYPHS})
|
||||
|
||||
|
||||
def crew_table(table: dict[str, Any]) -> bool:
|
||||
"""작업조 + 시공량으로 적힌 표인가."""
|
||||
hay = " ".join(norm(h) for h in table.get("headers", []))
|
||||
@@ -532,6 +555,8 @@ def build() -> dict[str, Any]:
|
||||
"spaced_names": spaced_names(table),
|
||||
# ⚠ 공식 기호 줄 — 소요량이 아니다. 자원으로 세면 이중계상.
|
||||
"formula_rows": formula_rows(table),
|
||||
# ⚠ 규격 표기 특수문자 — 갈래 키를 맞출 때 대조할 자리(값은 안 고침).
|
||||
"special_glyphs": special_glyphs(table),
|
||||
# 공식 기호가 이 표에 직접 있는가 — 없으면 앞 표에서 물려받는 모양이다.
|
||||
"capacity_formula_here": capacity_formula_pending(table),
|
||||
"variant_key": variant_axis(table),
|
||||
|
||||
@@ -499,6 +499,9 @@ def _earthwork_rows(
|
||||
"spec_detail": "",
|
||||
"composite_parts": None,
|
||||
"structure_kind": None,
|
||||
# 토공·운반 줄에는 갈래 축이 없다 — 그래도 **칸은 둔다**(계약이 한 모양).
|
||||
"variant_axis": None,
|
||||
"variant_value": None,
|
||||
"spec_class": None,
|
||||
"spec_class_basis": "",
|
||||
# 토공 줄은 막힐 자리가 없다 — 그래도 **칸은 둔다**(계약이 한 모양이어야 한다).
|
||||
@@ -551,6 +554,9 @@ def _haul_rows(
|
||||
"spec_detail": "",
|
||||
"composite_parts": None,
|
||||
"structure_kind": None,
|
||||
# 토공·운반 줄에는 갈래 축이 없다 — 그래도 **칸은 둔다**(계약이 한 모양).
|
||||
"variant_axis": None,
|
||||
"variant_value": None,
|
||||
"spec_class": None,
|
||||
"spec_class_basis": "",
|
||||
"blocked_kind": None,
|
||||
@@ -610,6 +616,7 @@ def _structure_rows(
|
||||
bond = str((structure.get("options") or {}).get("bond") or "").strip()
|
||||
bond_codes = entry.get("bond_codes") or {}
|
||||
if bond in bond_codes:
|
||||
# 메/찰은 **의미 판정**이라 우리 몫이다 — 공종 자체가 갈린다.
|
||||
code = bond_codes[bond]
|
||||
class_key = bond
|
||||
class_basis = f"쌓기 방식 「{bond}」 → 품셈 13-6 {code.split('-')[-1]}"
|
||||
@@ -620,8 +627,7 @@ def _structure_rows(
|
||||
)
|
||||
if code and entry.get("class_from") == "back_length":
|
||||
class_key, class_basis = masonry_class(structure.get("options") or {})
|
||||
if class_key:
|
||||
code = f"{code}#{normalize_kind_key(class_key)}"
|
||||
|
||||
composite = mapping.composite_for(type_id) if code is None else None
|
||||
kind = structure_kind(structure) if composite else None
|
||||
parts: list[dict[str, Any]] | None = None
|
||||
@@ -629,6 +635,9 @@ def _structure_rows(
|
||||
if composite:
|
||||
parts, parts_missing = composite_quantities(structure, composite, mapping)
|
||||
blocked_kind, blocked_reason = blocked_of(structure, class_basis)
|
||||
# 갈래 축과 **저장 제원 원본값**. 가공하지 않는다.
|
||||
variant_axis = str(entry.get("variant_axis") or "") or None
|
||||
variant_value = (structure.get("options") or {}).get(variant_axis) if variant_axis else None
|
||||
if code is None and composite is None:
|
||||
unmatched.append(f"{wording_type_label(type_id)} — 품셈 공종을 아직 못 이었습니다")
|
||||
elif entry.get("class_from") in ("back_length", "bond") and class_key is None:
|
||||
@@ -660,6 +669,12 @@ def _structure_rows(
|
||||
"blocked_kind": blocked_kind,
|
||||
"blocked_reason": blocked_reason,
|
||||
# 규격 갈래(뒷길이 …㎝ 이하) — 못 고르면 사유가 남는다.
|
||||
# ⚠ **갈래 키 문자열을 우리가 조립하지 않는다** (2026-09-07 계약 변경).
|
||||
# 품셈 원문이 물결표를 섞어 쓴다(`∼` U+223C / `~` U+FF5E). 두 창이 각자
|
||||
# 키를 조립하면 **글자 하나로 영영 안 맞는다.** 우리는 **어느 축인지와
|
||||
# 저장 원본값**만 보내고, 원문을 읽는 쪽이 그 표기를 흡수한다.
|
||||
"variant_axis": variant_axis,
|
||||
"variant_value": variant_value,
|
||||
"spec_class": class_key,
|
||||
"spec_class_basis": class_basis,
|
||||
# ⚠ 물량을 못 채운 조각 — 0 으로 적지 않고 사유와 함께 드러낸다.
|
||||
|
||||
@@ -119,16 +119,16 @@
|
||||
"work_item_code": "FP-13-04-05",
|
||||
"master_name": "돌쌓기 > 찰쌓기(장비)",
|
||||
"note": "인력 시공이면 FP-13-04-04",
|
||||
"class_from": "back_length",
|
||||
"class_note": "저장 제원 `back_len_cm` 으로 「…㎝ 이하」 구간을 고름 — 자동 판정"
|
||||
"class_note": "⚠ 갈래 키 문자열을 여기서 만들지 않는다 — `variant_axis`+`variant_value`(저장 원본값)만 보내고 「…㎝ 이하」 구간 나누기는 **품셈 원문을 읽는 쪽**이 한다(2026-09-07 계약).",
|
||||
"variant_axis": "back_len_cm"
|
||||
},
|
||||
{
|
||||
"type_id": "masonry_dry",
|
||||
"work_item_code": "FP-13-04-02",
|
||||
"master_name": "돌쌓기 > 메쌓기(장비)",
|
||||
"note": "인력 시공이면 FP-13-04-01",
|
||||
"class_from": "back_length",
|
||||
"class_note": "저장 제원 `back_len_cm` 으로 「…㎝ 이하」 구간을 고름 — 자동 판정"
|
||||
"class_note": "⚠ 갈래 키 문자열을 여기서 만들지 않는다 — `variant_axis`+`variant_value`(저장 원본값)만 보내고 「…㎝ 이하」 구간 나누기는 **품셈 원문을 읽는 쪽**이 한다(2026-09-07 계약).",
|
||||
"variant_axis": "back_len_cm"
|
||||
},
|
||||
{
|
||||
"type_id": "pipe_inlet_basin",
|
||||
@@ -148,7 +148,8 @@
|
||||
"메쌓기": "FP-13-06-01",
|
||||
"찰쌓기": "FP-13-06-02"
|
||||
},
|
||||
"class_note": "저장 제원 `bond`(메쌓기/찰쌓기)로 품셈 13-6-1·13-6-2 를 고름 — 자동 판정"
|
||||
"class_note": "메/찰(`bond`)은 **공종 자체가 갈리는 의미 판정**이라 여기서 고른다. 직경 갈래는 `variant_value` 로 원본값만 보낸다 — 원문이 물결표를 섞어 써서.",
|
||||
"variant_axis": "stone_cm"
|
||||
}
|
||||
],
|
||||
"pending_user": {
|
||||
@@ -264,5 +265,17 @@
|
||||
"hand_mixed": 408327
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"variant_contract": {
|
||||
"note": "갈래 계약(2026-09-07 3자) — **키 문자열을 두 창이 각자 조립하지 않는다.**",
|
||||
"why": "품셈 원문이 물결표를 섞어 쓴다: 13-06-01·02 는 `∼`(U+223C), 13-06-03 은 `~`(U+FF5E). 각자 조립하면 글자 하나로 영영 안 맞는다.",
|
||||
"b08_sends": [
|
||||
"work_item_code",
|
||||
"variant_axis",
|
||||
"variant_value",
|
||||
"kind_basis"
|
||||
],
|
||||
"b09_does": "원문 표기(물결표·공백·괄호)를 흡수해 자기 키로 옮긴다. 「…㎝ 이하」 구간 나누기도 그쪽 몫 — 그 구간이 **품셈 표의 구조**이기 때문."
|
||||
},
|
||||
"masonry_class_reference": "resources/data_masonry/masonry_class_2026-01-01.json — ⚠ 이제 **참고용**이다. 서브 판정과 어긋나면 그것이 곧 신호다."
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "data_work_item_master_manifest",
|
||||
"generated_at": "2026-09-08T01:52:34+09:00",
|
||||
"generated_at": "2026-09-08T04:57:04+09:00",
|
||||
"built_by": "B08_Quantity/B08_Quantity_Build_WorkItemMaster.py",
|
||||
"source": {
|
||||
"dataset_id": "pum_forest",
|
||||
@@ -12,8 +12,8 @@
|
||||
"files": [
|
||||
{
|
||||
"file": "work_item_master_2026-01-01.json",
|
||||
"sha256": "08b0c7c26c4aa569ace3b13490b8cbd00606254abb1439894eac750d387bfb03",
|
||||
"size_bytes": 839464
|
||||
"sha256": "1c49012f9f50d721676ad8def15c87c96b77aad83123286dd6981b7d9cefcd81",
|
||||
"size_bytes": 856450
|
||||
},
|
||||
{
|
||||
"file": "form_undetermined_2026-01-01.json",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user