102 lines
4.4 KiB
Python
102 lines
4.4 KiB
Python
"""M01 요소 「별칭」 칸 풀이 정본(PLAN 815-1 · 요구 764) — 인력 · 기계 · 재료 · 유가 줄의 `별칭` 글 하나.
|
||
|
||
별칭 글 = 콤마(「,」 · 전각 「,」)로 여러 개 · 앞뒤 빈칸 뗌 · 큰따옴표(" · “ ”)로 감싼 조각 = 통째
|
||
한 별칭(속 콤마 보존 — 이름과 규격을 같이 적을 때 「"결속선(철선) #20, 0.9M/M"」) · 빈 칸 = 없음.
|
||
`norm` = 표기 정규화(빈칸 · 전각 · 대소문자 · 괄호 · 단위 M/M = mm · 곱셈표 · 지름 Φ · D= · 번호 #) —
|
||
이름 낱말 순서(「결속선(철선)」 ↔ 「철선(결속선)」)는 맞추지 않음(별칭 몫).
|
||
뜻: 별칭 = 그 줄을 가리키는 다른 이름 — 이름만 적은 별칭은 줄 규격이 정규화해 같을 때 그 줄 ·
|
||
이름 + 규격을 적은 별칭은 「이름 규격」 통째가 같을 때 그 줄 · 여럿 = 부르는 쪽이 「후보 여럿」.
|
||
쓰는 곳 = M02 불러오기 요소 잇기(`M02_Elem_Alias`) · M01 별칭 칸 저장 · 보기. 서버 단독 · 읽기만.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
import unicodedata
|
||
from collections.abc import Callable, Iterable
|
||
from typing import Any
|
||
|
||
_QUOTES = {'"': '"', "“": "”", "”": "”"}
|
||
_SEP = (",", ",")
|
||
_TIMES = re.compile(r"(?<=\d)\s*[x×*]\s*(?=\d)")
|
||
_DIAM = re.compile(r"[φϕΦøØ∅]|(?<![a-z가-힣])d\s*=\s*(?=\d)")
|
||
_BRACKETS = re.compile(r"[()\[\]{}〔〕「」『』<>]")
|
||
|
||
|
||
def names(text: Any) -> list[str]:
|
||
"""별칭 글 → 별칭 목록(차례 그대로 · 같은 것 한 번)."""
|
||
out: list[str] = []
|
||
piece, close = "", None
|
||
for ch in f"{text or ''},":
|
||
if close:
|
||
if ch == close or (close == "”" and ch == '"'):
|
||
close = None
|
||
else:
|
||
piece += ch
|
||
elif ch in _QUOTES and not piece.strip():
|
||
close, piece = _QUOTES[ch], ""
|
||
elif ch in _SEP:
|
||
one = piece.strip()
|
||
if one and one not in out:
|
||
out.append(one)
|
||
piece = ""
|
||
else:
|
||
piece += ch
|
||
if close and piece.strip() and piece.strip() not in out: # 닫는 따옴표 빠짐 = 끝까지 한 조각
|
||
out.append(piece.strip())
|
||
return out
|
||
|
||
|
||
def norm(text: Any) -> str:
|
||
"""표기 정규화 — 같은 글이면 같은 값(맞대기 열쇠)."""
|
||
t = unicodedata.normalize("NFKC", str(text or "")).lower()
|
||
t = t.replace("m/m", "mm")
|
||
t = _TIMES.sub("x", t).replace("×", "x")
|
||
t = _DIAM.sub("φ", t)
|
||
t = _BRACKETS.sub("", t)
|
||
return re.sub(r"\s+", "", t)
|
||
|
||
|
||
def index(rows: Iterable[dict[str, Any]]) -> dict[str, list[str]]:
|
||
"""요소 줄들(`키` · `별칭`) → {정규화 별칭: [키 …]}(줄 차례)."""
|
||
out: dict[str, list[str]] = {}
|
||
for row in rows:
|
||
key = row.get("키")
|
||
for one in names(row.get("별칭")) if key else ():
|
||
got = out.setdefault(norm(one), [])
|
||
if str(key) not in got:
|
||
got.append(str(key))
|
||
return out
|
||
|
||
|
||
def match(
|
||
idx: dict[str, list[str]], spec_of: Callable[[str], Any], name: Any, spec: Any = ""
|
||
) -> list[str]:
|
||
"""가져온 줄(이름 · 규격) → 별칭으로 이어지는 키들 — 「이름 규격」 통째 별칭 › 이름 별칭 + 줄 규격
|
||
정규화 같음 › (가져온 규격 빔) 이름 별칭 줄 하나 · 없음 = []."""
|
||
mine = norm(spec)
|
||
if mine and (whole := idx.get(norm(name) + mine)):
|
||
return list(whole)
|
||
named = idx.get(norm(name)) or []
|
||
same = [k for k in named if norm(spec_of(k)) == mine]
|
||
if same:
|
||
return same
|
||
return list(named) if not mine and len(named) == 1 else []
|
||
|
||
|
||
if __name__ == "__main__":
|
||
assert names('철선, 결속선 ,"결속선(철선) #20, 0.9M/M" , ,철선') == [
|
||
"철선", "결속선", "결속선(철선) #20, 0.9M/M"
|
||
] # fmt: skip
|
||
assert (
|
||
norm("결속선(철선) #20, 0.9M/M") == norm("결속선 철선#20,0.9㎜") == "결속선철선#20,0.9mm"
|
||
)
|
||
assert norm("600×1200") == norm("600 X 1200") == norm("600*1200") == "600x1200"
|
||
assert norm("PVC D=100") == norm("PVC Φ100") == norm("pvc ø 100") == "pvcφ100"
|
||
idx = index([{"키": "A", "별칭": "철선"}, {"키": "B", "별칭": '"철선 #8"'}])
|
||
specs = {"A": "#20, 0.9mm", "B": "#8"}
|
||
assert match(idx, specs.get, "철선", "#20, 0.9M/M") == ["A"]
|
||
assert match(idx, specs.get, "철선", "#8") == ["B"]
|
||
assert match(idx, specs.get, "철선", "#10") == []
|
||
print("ok")
|