refactor(aliases): 별칭표를 한 벌로 — 범위(scope)·판을 붙여 B08·B09 가 같은 파일만 읽음
- 정본 resources/data_aliases/aliases_2026-01-01.json · 로더 common_util/common_util_aliases.py (칸 빠짐·모르는 축·겹친 범위의 두 대상은 읽을 때 오류 · 판이 다르면 안 씀 · 양방향 조회) - 흩어진 세 벌을 옮김: 매핑표 ground_aliases(리핑암→파쇄암·암절취) 삭제 · B09 UnitPrice 전역 VARIANT_ALIASES 삭제 · B08 인계본 ground_class_aliases 는 별칭표에서 만든 보기 - 범위: 리핑암→파쇄암 @FP-10-11 · 리핑암→암절취 @FP-10-12 · 화약공→1016 @FP-09-05 (근거 문구 줄마다) - 자원 축 별칭 검사도 같은 로더로 합침(자원 목록 폴더에서 별칭 파일을 옮김) - 시험 test_common_aliases.py 6건 · 전체 1509 통과 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
@@ -205,6 +205,7 @@ class WorkItemMapping:
|
||||
pipe: dict[str, Any] = field(default_factory=dict)
|
||||
#: 갈래 이름 별칭 — 우리 「리핑암」 ↔ 일위대가 「파쇄암」처럼 **같은 것을 다른 이름**으로
|
||||
#: 부르는 자리. ⚠ 갈래 이름 자체를 갈지 않는다(흙깎기 매핑이 그 이름으로 서 있다).
|
||||
#: ⚠ 정본은 **별칭표 한 벌**(`common_util_aliases`)이다 — 여기는 인계본에 싣는 보기일 뿐.
|
||||
ground_aliases: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def declared_units(self) -> dict[str, str]:
|
||||
@@ -263,6 +264,11 @@ def load_mapping(path: Path | None = None) -> WorkItemMapping:
|
||||
if target is None or not target.is_file():
|
||||
return WorkItemMapping()
|
||||
payload = json.loads(target.read_text(encoding="utf-8"))
|
||||
from common_util.common_util_aliases import load_aliases, names_view
|
||||
|
||||
# 별칭은 매핑표가 아니라 별칭표 한 벌에서 온다 — **매핑이 기대는 공종 마스터 판**의 줄만 싣는다.
|
||||
edition = str((payload.get("master") or {}).get("effective_date") or "")
|
||||
variant_aliases = [row for row in load_aliases("variant") if row["pum_edition"] == edition]
|
||||
return WorkItemMapping(
|
||||
effective_date=str(payload.get("effective_date") or ""),
|
||||
earthwork=list(payload.get("earthwork") or []),
|
||||
@@ -274,7 +280,7 @@ def load_mapping(path: Path | None = None) -> WorkItemMapping:
|
||||
concrete_placing=payload.get("concrete_placing") or {},
|
||||
pipe=payload.get("pipe") or {},
|
||||
unit_conversion=(payload.get("composite") or {}).get("unit_conversion") or {},
|
||||
ground_aliases=payload.get("ground_aliases") or {},
|
||||
ground_aliases={"aliases": names_view(variant_aliases)} if variant_aliases else {},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
품셈 칸이 규격을 안 주면 「규격 미정 — 후보 N」으로 드러낸다.
|
||||
② **형식이 하나뿐인 계열 이름** — 「공기압축기(3.5㎥/min)」 → 「공기압축기(이동식)」 3.5.
|
||||
형식이 둘 이상이면 고르지 않는다(공압식·전기식은 다른 장비 · 판정 Ⓒ).
|
||||
③ **범위 별칭** `{from, to, scope, pum_edition}` — 공종 코드 범위 안에서만 바꾼다(명세 5장).
|
||||
③ **범위 별칭** — 별칭표 한 벌(`common_util_aliases`)의 `resource` 줄을 공종 범위 안에서만 쓴다.
|
||||
scope 없는 줄 · 겹친 범위에서 두 코드로 가는 이름은 **읽을 때 오류**로 세운다.
|
||||
⚠ 대체(대응이 아예 없어 갈음)는 별칭이 아니다 — 여기 넣지 않는다.
|
||||
|
||||
@@ -34,10 +34,10 @@ from B09_Estimation.B09_Estimation_ResourceAxis_Sources import (
|
||||
parse_machine_cell,
|
||||
split_name_and_spec,
|
||||
)
|
||||
from common_util.common_util_aliases import AliasError, in_scope, load_aliases, parse_aliases
|
||||
|
||||
_EXT_SUBPATH = ("resources", "data_resource_catalog")
|
||||
EXT_CATALOG_FILE = "resource_catalog_ext_2026-01-01.json"
|
||||
ALIASES_FILE = "aliases_2026-01-01.json"
|
||||
|
||||
#: 명세 §2 ③ — `AR-<M|L|X>-<8자리 소문자 16진>`. 종류 글자는 PriceKind 글자 그대로.
|
||||
_RE_AR_CODE = re.compile(r"^AR-([MLX])-[0-9a-f]{8}$")
|
||||
@@ -94,45 +94,17 @@ def load_ext_entries(file_name: str = EXT_CATALOG_FILE) -> list[CatalogEntry]:
|
||||
return parse_ext_entries(list(_read_optional(file_name).get("entries") or []))
|
||||
|
||||
|
||||
def in_scope(work_item_code: str, scope: str) -> bool:
|
||||
"""`FP-*` 범위 — 그 코드 자신이거나 그 아래. ⚠ `AX`·`AR` 에는 계층이 없어 쓰지 않는다."""
|
||||
return work_item_code == scope or work_item_code.startswith(scope + "-")
|
||||
|
||||
|
||||
def parse_scoped_aliases(rows: list[dict[str, Any]]) -> list[dict[str, str]]:
|
||||
"""자원 축 별칭만 골라 검사한다. **scope 없는 줄 · 겹친 범위의 두 코드는 오류.**"""
|
||||
aliases: list[dict[str, str]] = []
|
||||
for row in rows:
|
||||
if row.get("axis") != "resource":
|
||||
continue
|
||||
picked = {
|
||||
key: str(row.get(key) or "").strip() for key in ("from", "to", "scope", "pum_edition")
|
||||
}
|
||||
if not all(picked.values()):
|
||||
raise ResourceAxisError(
|
||||
f"별칭 줄에 from·to·scope·pum_edition 이 다 있어야 합니다: {row}"
|
||||
)
|
||||
aliases.append(picked)
|
||||
for index, left in enumerate(aliases):
|
||||
for right in aliases[index + 1 :]:
|
||||
if (
|
||||
_normalize(left["from"]) == _normalize(right["from"])
|
||||
and left["to"] != right["to"]
|
||||
and left["pum_edition"] == right["pum_edition"]
|
||||
and (
|
||||
in_scope(left["scope"], right["scope"])
|
||||
or in_scope(right["scope"], left["scope"])
|
||||
)
|
||||
):
|
||||
raise ResourceAxisError(
|
||||
f"같은 이름 「{left['from']}」이 겹친 범위에서 두 코드로 갑니다: "
|
||||
f"{left['to']} · {right['to']}"
|
||||
)
|
||||
return aliases
|
||||
"""자원 축 별칭만 — 검사(scope 없음 · 겹친 범위의 두 코드는 오류)는 별칭표 한 벌이 한다."""
|
||||
try:
|
||||
return [row for row in parse_aliases(rows) if row["axis"] == "resource"]
|
||||
except AliasError as error:
|
||||
raise ResourceAxisError(str(error)) from error
|
||||
|
||||
|
||||
def load_scoped_aliases(file_name: str = ALIASES_FILE) -> list[dict[str, str]]:
|
||||
return parse_scoped_aliases(list(_read_optional(file_name).get("aliases") or []))
|
||||
def load_scoped_aliases() -> list[dict[str, str]]:
|
||||
"""자원 축 별칭 — 정본은 `resources/data_aliases/`(B08·B09 한 벌)."""
|
||||
return load_aliases("resource")
|
||||
|
||||
|
||||
def apply_scoped_alias(catalog: ResourceCatalog, name_cell: str, work_item_code: str) -> str:
|
||||
|
||||
@@ -461,23 +461,16 @@ def normalize_variant_key(text: str) -> str:
|
||||
return "".join("~" if ch in _TILDE_CHARS else ch for ch in tight)
|
||||
|
||||
|
||||
#: 갈래 이름이 **축이 달라 다르게 불리는** 자리 — 이름을 갈지 않고 여기서 잇는다.
|
||||
#:
|
||||
#: ⚠ **우리 축과 품셈 축이 다르다.** 우리 「리핑암」은 B05·B06 의 **지반유형**(캘 수 있는가)이
|
||||
#: 낳은 이름이고, 품셈 운반표의 「파쇄암」은 **운반할 때의 상태**(부서졌는가)를 가리킨다.
|
||||
#: 그래서 일반적으로는 「리핑암 = 파쇄암」이 아니다 — **발파암도 캐고 나면 파쇄암 상태**다
|
||||
#: (건설품셈 8장 「발파 또는 리퍼작업 등에 의하여 얻어진 암과 파쇄암…」).
|
||||
#:
|
||||
#: ⭐ **다만 이 표 안에서는 성립한다.** 산림품셈 10-11 f 표가 **토사·파쇄암·발파암을 따로**
|
||||
#: 두었으므로 그 표의 「파쇄암」은 **발파를 뺀 나머지 = 리퍼로 얻은 것**이다. 결정적 증거는
|
||||
#: 10-12 [주]③ 이다 — 「적재 재료의 토량환산계수(L)는 토사 1.3, **암절취 1.35**, 발파암
|
||||
#: 1.625 적용한다」. 10-11 f 표의 **파쇄암이 1/1.35** 라 **암절취(리핑)과 같은 값**이다.
|
||||
#:
|
||||
#: ⚠ **어느 쪽 이름도 갈지 않는다** — 우리 이름을 갈면 B05·B06 이 깨지고, 품셈 이름을 갈면
|
||||
#: 원문과 어긋난다. 잇는 자리는 여기 한 곳뿐이다.
|
||||
VARIANT_ALIASES: dict[str, str] = {
|
||||
"리핑암": "파쇄암",
|
||||
}
|
||||
#: 갈래 이름이 **축이 달라 다르게 불리는** 자리(우리 「리핑암」 ↔ 품셈 10-11 f 「파쇄암」)는
|
||||
#: **별칭표 한 벌**(`common_util_aliases`, axis `variant`)에서 **범위째** 읽는다(2026-09-13 이관).
|
||||
#: ⚠ 「리핑암 = 파쇄암」은 10-11 f 표 안에서만 참이다 — 발파암도 캐고 나면 파쇄암 상태라 전역으로
|
||||
#: 잇으면 틀린다(근거 문구는 별칭표 줄의 `basis` 에 옮겨 둠). 어느 쪽 이름도 갈지 않는다.
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _master_edition() -> str:
|
||||
"""별칭 범위가 묶인 품셈 판 — 지금 공종 마스터의 판(명세 17장)."""
|
||||
return str(load_work_item_master().get("effective_date", ""))
|
||||
|
||||
|
||||
def find_variant_code(
|
||||
@@ -494,7 +487,10 @@ def find_variant_code(
|
||||
wanted = normalize_variant_key(variant_value)
|
||||
if not wanted:
|
||||
return None
|
||||
wanted = VARIANT_ALIASES.get(wanted, wanted)
|
||||
from common_util.common_util_aliases import alias_target, load_aliases
|
||||
|
||||
alias = alias_target(load_aliases("variant"), wanted, work_item_code, _master_edition())
|
||||
wanted = normalize_variant_key(alias) if alias else wanted
|
||||
|
||||
prefix = f"B-{work_item_code}#"
|
||||
candidates = [code for code in prices.book.titles if code.startswith(prefix)]
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""별칭표 한 벌 — 우리 이름 ↔ 품셈·실무 이름 (2026-09-13 축 C 명세 5장·17장).
|
||||
|
||||
정본 파일 `resources/data_aliases/aliases_<판>.json`. **B08·B09 는 이 파일만 읽는다** — 매핑표·
|
||||
단가 코드·인계본에 따로 두었더니 세 벌이 서로 달라졌다(리핑암 → 파쇄암·암절취 / 파쇄암만 / 안 읽음).
|
||||
|
||||
한 줄 = `{axis, from, to, scope, pum_edition, basis}`
|
||||
axis `resource`(자원 이름 → 카탈로그 코드) · `variant`(우리 갈래 이름 → 품셈 갈래 이름)
|
||||
scope 이 별칭이 **참인 `FP-*` 범위** — 없으면 오류. 별칭은 표 범위에서만 참이다
|
||||
(「리핑암 = 파쇄암」은 10-11 f 표 안에서만 — 발파암도 캐고 나면 파쇄암 상태다)
|
||||
pum_edition scope 가 묶인 품셈 판 — 판이 다르면 쓰지 않는다(`FP-*` 는 판 안에서만 유일)
|
||||
|
||||
⚠ **별칭 ≠ 대체** — 대응이 아예 없어 갈음하는 것은 여기 넣지 않는다(설계가 고른다).
|
||||
⚠ 같은 이름이 **겹친 범위**에서 두 곳으로 가면 **읽을 때 오류** — 조용히 첫째를 고르지 않는다.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ALIAS_FILE = (
|
||||
Path(__file__).resolve().parents[1] / "resources" / "data_aliases" / "aliases_2026-01-01.json"
|
||||
)
|
||||
AXES = frozenset({"resource", "variant"})
|
||||
_FIELDS = ("axis", "from", "to", "scope", "pum_edition")
|
||||
|
||||
|
||||
class AliasError(ValueError):
|
||||
"""별칭표가 규약을 어겼다 — 조용히 넘기지 않는다."""
|
||||
|
||||
|
||||
def _key(text: str) -> str:
|
||||
"""이름 비교 키 — 내부 공백만 지운다(「화 약 공」 = 「화약공」). 다른 글자는 안 건드린다."""
|
||||
return re.sub(r"\s+", "", str(text or ""))
|
||||
|
||||
|
||||
def in_scope(work_item_code: str, scope: str) -> bool:
|
||||
"""`FP-*` 범위 — 그 코드 자신이거나 그 아래. ⚠ `AX`·`AR` 코드에는 계층이 없어 쓰지 않는다."""
|
||||
return work_item_code == scope or work_item_code.startswith(scope + "-")
|
||||
|
||||
|
||||
def parse_aliases(rows: list[dict[str, Any]]) -> list[dict[str, str]]:
|
||||
"""줄을 검사해 돌려준다. 칸 빠짐 · 모르는 축 · 겹친 범위의 두 대상은 오류."""
|
||||
aliases: list[dict[str, str]] = []
|
||||
for row in rows:
|
||||
picked = {key: str(row.get(key) or "").strip() for key in _FIELDS}
|
||||
if not all(picked.values()):
|
||||
raise AliasError(f"별칭 줄에 {'·'.join(_FIELDS)} 가 다 있어야 합니다: {row}")
|
||||
if picked["axis"] not in AXES:
|
||||
raise AliasError(f"모르는 별칭 축입니다: {picked['axis']}")
|
||||
picked["basis"] = str(row.get("basis") or "")
|
||||
aliases.append(picked)
|
||||
for index, left in enumerate(aliases):
|
||||
for right in aliases[index + 1 :]:
|
||||
if (
|
||||
left["axis"] == right["axis"]
|
||||
and _key(left["from"]) == _key(right["from"])
|
||||
and left["to"] != right["to"]
|
||||
and left["pum_edition"] == right["pum_edition"]
|
||||
and (
|
||||
in_scope(left["scope"], right["scope"])
|
||||
or in_scope(right["scope"], left["scope"])
|
||||
)
|
||||
):
|
||||
raise AliasError(
|
||||
f"같은 이름 「{left['from']}」이 겹친 범위에서 두 곳으로 갑니다: "
|
||||
f"{left['to']}({left['scope']}) · {right['to']}({right['scope']})"
|
||||
)
|
||||
return aliases
|
||||
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def _load(path: str) -> tuple[dict[str, str], ...]:
|
||||
target = Path(path)
|
||||
if not target.is_file():
|
||||
return () # 파일이 없으면 별칭 없이 돈다 — 못 맞춘 줄로 드러난다
|
||||
payload = json.loads(target.read_text(encoding="utf-8"))
|
||||
return tuple(parse_aliases(list(payload.get("aliases") or [])))
|
||||
|
||||
|
||||
def load_aliases(axis: str, path: Path | None = None) -> list[dict[str, str]]:
|
||||
"""그 축의 별칭 줄. 파일은 한 번만 읽는다."""
|
||||
return [dict(row) for row in _load(str(path or ALIAS_FILE)) if row["axis"] == axis]
|
||||
|
||||
|
||||
def alias_target(
|
||||
rows: list[dict[str, str]],
|
||||
name: str,
|
||||
work_item_code: str,
|
||||
edition: str,
|
||||
*,
|
||||
reverse: bool = False,
|
||||
) -> str | None:
|
||||
"""범위·판이 맞는 줄에서 이름을 옮긴다. `reverse` 면 남의 이름 → 우리 이름(양방향 조회)."""
|
||||
source, target = ("to", "from") if reverse else ("from", "to")
|
||||
wanted = _key(name)
|
||||
for row in rows:
|
||||
if (
|
||||
row["pum_edition"] == edition
|
||||
and _key(row[source]) == wanted
|
||||
and in_scope(work_item_code, row["scope"])
|
||||
):
|
||||
return row[target]
|
||||
return None
|
||||
|
||||
|
||||
def names_view(rows: list[dict[str, str]]) -> dict[str, dict[str, Any]]:
|
||||
"""인계본 모양 `{우리 이름: {names, scopes, basis}}` — B08 `ground_class_aliases` 가 싣는다."""
|
||||
view: dict[str, dict[str, Any]] = {}
|
||||
for row in rows:
|
||||
entry = view.setdefault(row["from"], {"names": [], "scopes": {}, "basis": ""})
|
||||
entry["names"].append(row["to"])
|
||||
entry["scopes"][row["to"]] = row["scope"]
|
||||
entry["basis"] = " · ".join(part for part in (entry["basis"], row["basis"]) if part)
|
||||
return view
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "aliases",
|
||||
"note": "별칭표 한 벌(명세 5장) — B08·B09 가 이 파일만 읽는다(`common_util/common_util_aliases.py`). 한 줄 = {axis, from, to, scope, pum_edition, basis}. axis: resource(자원 이름 → 카탈로그 코드) · variant(우리 갈래 이름 → 품셈 갈래 이름). scope 없는 줄은 오류 · 같은 이름이 겹친 범위에서 두 곳으로 가면 오류. 대체(대응이 아예 없어 갈음)는 넣지 않음. scope 는 품셈 판에 묶임 — 판이 다르면 적용 안 함(명세 17장).",
|
||||
"aliases": [
|
||||
{
|
||||
"axis": "resource",
|
||||
"from": "화약공",
|
||||
"to": "1016",
|
||||
"scope": "FP-09-05",
|
||||
"pum_edition": "2026-01-01",
|
||||
"basis": "노임 표준 직종명은 「화약취급공」(노임 카탈로그 1016) — 품셈 9-5 표가 줄여 적음. 2026-09-13 사용자 위임 판정(브레인)"
|
||||
},
|
||||
{
|
||||
"axis": "variant",
|
||||
"from": "리핑암",
|
||||
"to": "파쇄암",
|
||||
"scope": "FP-10-11",
|
||||
"pum_edition": "2026-01-01",
|
||||
"basis": "품셈 10-11 f 표가 토사·파쇄암·발파암을 따로 둠 — 그 표 안에서만 파쇄암 = 발파를 뺀 리퍼 암. 결정적 근거 10-12 [주]③ 환산계수 「암절취 1.35」 = 10-11 f 「파쇄암 1/1.35」. ⚠ 일반적으로는 발파암도 캐면 파쇄암 상태라 전역 별칭 금지. 도자 운반 일위대가가 파쇄암 이름으로 서 있어 이름만 못 이어 금액이 안 붙던 자리(2026-09-08 데스크탑 보조 원문 대조)"
|
||||
},
|
||||
{
|
||||
"axis": "variant",
|
||||
"from": "리핑암",
|
||||
"to": "암절취",
|
||||
"scope": "FP-10-12",
|
||||
"pum_edition": "2026-01-01",
|
||||
"basis": "품셈 10-12 덤프 운반 잎이 토사·암절취·발파암으로 갈리고 [주]③ 환산계수가 「암절취 1.35」(10-11 f 파쇄암 1/1.35 와 같은 값) — 우리 리핑암(리퍼 절취) = 품셈 암절취. 옛 매핑표 ground_aliases 에서 범위를 붙여 옮김(2026-09-13). ⚠ 쓰는 곳: 덤프 운반 잎 고르기(아직 없음 — 갈래 고르기형 부모 판정 대기)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"dataset_id": "aliases",
|
||||
"note": "별칭 한 벌(명세 5장). 한 줄 = {axis, from, to, scope, pum_edition, basis}. scope 없는 줄은 오류 · 같은 이름이 겹친 범위에서 두 코드로 가면 오류. 대체(대응이 아예 없어 갈음)는 여기 넣지 않음. scope 는 품셈 판에 묶임 — 판이 다르면 적용 안 함(명세 17장).",
|
||||
"aliases": [
|
||||
{
|
||||
"axis": "resource",
|
||||
"from": "화약공",
|
||||
"to": "1016",
|
||||
"scope": "FP-09-05",
|
||||
"pum_edition": "2026-01-01",
|
||||
"basis": "노임 표준 직종명은 「화약취급공」(노임 카탈로그 1016) — 품셈 9-5 표가 줄여 적음. 2026-09-13 사용자 위임 판정(브레인)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -393,16 +393,5 @@
|
||||
"터파기·되메우기": "⚠ 관 부설과 터파기·되메우기가 각각 오면 **같은 굴착을 두 번 셀 수 있다**(B09 ㉡ 가드). 관 줄에는 지금 터파기를 붙이지 않는다."
|
||||
}
|
||||
},
|
||||
"ground_aliases": {
|
||||
"note": "우리 갈래 이름 ↔ 일위대가·품셈이 쓰는 다른 이름. **값을 바꾸지 않고 이름만 잇는다** — 갈래 이름을 갈아 버리면 흙깎기(FP-09-04 리핑암) 매핑이 어긋난다.",
|
||||
"aliases": {
|
||||
"리핑암": {
|
||||
"names": [
|
||||
"파쇄암",
|
||||
"암절취"
|
||||
],
|
||||
"basis": "품셈 10-11 f 표가 「파쇄암 1/1.35」이고 10-12 [주]③ 환산계수가 「암절취 1.35」로 **같은 값**이다 ⇒ 암절취(리핑) = 파쇄암. 우리 일위대가에는 도자 운반이 「토사 1,711.9 · 파쇄암 3,911.3 · 발파암 4,708.1원/㎥」로 파쇄암 이름으로 서 있어, 이름만 못 이어 금액이 안 붙던 자리다(2026-09-08 데스크탑 보조 원문 대조)."
|
||||
}
|
||||
}
|
||||
}
|
||||
"ground_aliases_moved_to": "resources/data_aliases/aliases_2026-01-01.json — 갈래 이름 별칭은 범위(scope)·판을 붙여 별칭표 한 벌로 옮김(2026-09-13 축 C 명세 5장). 여기 다시 두지 말 것."
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
"""별칭표 한 벌 (2026-09-13 축 C 명세 5장·17장 · `common_util_aliases`).
|
||||
|
||||
지키는 것
|
||||
① 정본 파일의 모든 줄에 scope·pum_edition 이 있다
|
||||
② 별칭은 **범위 안·같은 판**에서만 옮긴다 — 「리핑암 → 파쇄암」은 10-11 에서만
|
||||
③ 양방향 조회 — 품셈 이름으로도 우리 이름을 찾는다
|
||||
④ scope 없음 · 모르는 축 · 겹친 범위의 두 대상은 읽을 때 오류
|
||||
⑤ B09 갈래 고르기와 B08 인계본이 **같은 파일**을 읽는다
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from B08_Quantity.B08_Quantity_Engine_Handoff import load_mapping
|
||||
from common_util.common_util_aliases import (
|
||||
ALIAS_FILE,
|
||||
AliasError,
|
||||
alias_target,
|
||||
load_aliases,
|
||||
parse_aliases,
|
||||
)
|
||||
|
||||
ROW = {
|
||||
"axis": "variant",
|
||||
"from": "리핑암",
|
||||
"to": "파쇄암",
|
||||
"scope": "FP-10-11",
|
||||
"pum_edition": "2026-01-01",
|
||||
}
|
||||
|
||||
|
||||
def test_정본_파일_모든_줄에_범위와_판():
|
||||
rows = parse_aliases(json.loads(ALIAS_FILE.read_text(encoding="utf-8"))["aliases"])
|
||||
assert rows
|
||||
assert all(row["scope"].startswith("FP-") and row["pum_edition"] for row in rows)
|
||||
|
||||
|
||||
def test_범위_안_같은_판에서만_옮긴다():
|
||||
rows = load_aliases("variant")
|
||||
assert alias_target(rows, "리핑암", "FP-10-11", "2026-01-01") == "파쇄암"
|
||||
assert alias_target(rows, "리 핑 암", "FP-10-12-02", "2026-01-01") == "암절취"
|
||||
assert alias_target(rows, "리핑암", "FP-09-19-02", "2026-01-01") is None
|
||||
assert alias_target(rows, "리핑암", "FP-10-11", "2027-01-01") is None
|
||||
assert alias_target(rows, "발파암", "FP-10-11", "2026-01-01") is None
|
||||
|
||||
|
||||
def test_양방향_조회():
|
||||
rows = load_aliases("variant")
|
||||
assert alias_target(rows, "파쇄암", "FP-10-11", "2026-01-01", reverse=True) == "리핑암"
|
||||
assert alias_target(rows, "파쇄암", "FP-10-12", "2026-01-01", reverse=True) is None
|
||||
|
||||
|
||||
def test_규약_어기면_읽을_때_오류():
|
||||
with pytest.raises(AliasError):
|
||||
parse_aliases([{**ROW, "scope": ""}])
|
||||
with pytest.raises(AliasError):
|
||||
parse_aliases([{**ROW, "axis": "ground"}])
|
||||
with pytest.raises(AliasError):
|
||||
parse_aliases([ROW, {**ROW, "to": "암절취", "scope": "FP-10"}])
|
||||
# 범위가 안 겹치거나 판이 다르면 같은 이름이 다른 곳으로 가도 된다
|
||||
assert len(parse_aliases([ROW, {**ROW, "to": "암절취", "scope": "FP-10-12"}])) == 2
|
||||
assert len(parse_aliases([ROW, {**ROW, "to": "암절취", "pum_edition": "2027-01-01"}])) == 2
|
||||
|
||||
|
||||
def test_B08_인계본은_범위째_싣는다():
|
||||
view = load_mapping().ground_aliases["aliases"]["리핑암"]
|
||||
assert view["scopes"] == {"파쇄암": "FP-10-11", "암절취": "FP-10-12"}
|
||||
|
||||
|
||||
def test_B09_갈래_고르기가_별칭표를_읽는다():
|
||||
from B09_Estimation.B09_Estimation_UnitPrice import find_variant_code
|
||||
|
||||
assert find_variant_code("FP-10-11", "리핑암") == "B-FP-10-11#파쇄암"
|
||||
assert find_variant_code("FP-10-11", "발파암") == "B-FP-10-11#발파암"
|
||||
Reference in New Issue
Block a user