Merge branch 'sub_laptop_1' of C:/Aislo_wt/sub_laptop_1 into main_laptop_1
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
"""M02 불러오기 재료 표기 다름 찾기(PLAN 817-1 · 요구 773-5) — `M02_Hopyo_Row._m01_picks` 재료 길이 부름.
|
||||
|
||||
서버 단독 · 읽기만 · 별칭 · 정확 일치 · 바탕 이름 유일 짝(`M02_Hopyo_Material.sure`)이 못 찾은 줄만.
|
||||
- `same(줄)` ㉮ 이음 = 이름이 같은 품목(표기만 다름) · 규격 수 묶음 같음 · 규격 낱말 안 부딪힘 · 그런 우리
|
||||
품목 하나(지역 · 관급/사급 줄은 한 품목) → 그 자재 줄 · 아니면 None.
|
||||
- `narrow(줄)` ㉯ 후보 좁힘 = 이름 같은 품목 묶음을 규격 낱말(어근 「초본류」 → 초본)을 품은 줄 · 수 묶음
|
||||
닮은 줄 · 덧말 적은 줄 차례로(자동 이음 아님 · 1 순위 맨 위).
|
||||
- `spec_first(후보, 줄)` = 후보 가운데 규격 수 묶음(둘 이상)이 줄과 같은 것 먼저.
|
||||
|
||||
이름 같음: 바탕(괄호 · 지역 · 관급/사급 꼬리 · 등급 「1급」 뗌 · 빈칸 · 점 「P.V.C」 · 소리 표기 `SPELL`) 같음 ·
|
||||
괄호 차례 「패널(유로폼)」 = 「유로폼패널」(글자 묶음 같음) · 괄호 이름 「서비스볼트(고장력볼트)」 = 고장력볼트 ·
|
||||
(약한 같음 — 수 묶음이 같을 때만) 끝 낱말 같음 「지수판」 ↔ 「PVC지수판」 · 앞 낱말 「구역화물자동차」 ↔ 구역화물 ·
|
||||
규격에 든 이름 「안고초」 ↔ 산림종자 안고초(가져온 규격 빔).
|
||||
수 묶음: 규격 + 이름 괄호의 수(차례 무관) · KS 번호 · 무게(kg · ㎏/포 · m/kg) · 제곱 꼬리(㎡ · ㎠) 뗌 ·
|
||||
천 단위 쉼표 · 같음 > 낱마다 단위 배(1 · 10 · 100 · 1000 — 3.6×3.0×3.0 m = 30×30×3600 mm) > 한쪽이 다른 쪽에 듦
|
||||
(이름 같을 때만 · 덧글 「2000 길이 · 20~30mm」).
|
||||
규격 낱말 부딪힘 = 두 쪽 다 괄호 · 규격 한글 낱말이 있고 두 글자 묶음이 하나도 안 겹침(「연강용」 ↔ 「주철용」) —
|
||||
상대 이름에 든 낱말은 뺌.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import unicodedata
|
||||
from functools import lru_cache
|
||||
from itertools import permutations
|
||||
from typing import Any
|
||||
|
||||
from M02_MasterTemplete import M02_Hopyo_Match as hm
|
||||
from M02_MasterTemplete import M02_Hopyo_Material as hmat
|
||||
|
||||
SPELL = (
|
||||
(r"칠렌", "틸렌"),
|
||||
(r"매쉬", "메쉬"),
|
||||
(r"신나(?![가-힣])", "신너"),
|
||||
(r"pipe", "파이프"),
|
||||
) # 소리 · 영문 표기
|
||||
NOISE = frozenset({"이내", "이하", "이상", "국산", "포함"})
|
||||
SCALES = (1, 10, 100, 1000)
|
||||
_STEM = re.compile(r"(?<=..)[류형용]$")
|
||||
_KS = re.compile(r"ks\s*[a-z]?\s*-?\s*\d+")
|
||||
_SQUARE = re.compile(r"(?:c|m)?m[23](?![\d.])")
|
||||
_THOUSAND = re.compile(r"(?<=\d),(?=\d{3}(?!\d))")
|
||||
_NUM = re.compile(r"\d+(?:\.\d+)?")
|
||||
_PAREN = re.compile(r"\(([^()]*)\)")
|
||||
|
||||
|
||||
def _low(text: Any) -> str:
|
||||
t = unicodedata.normalize("NFKC", str(text or "")).lower().replace("m/m", "mm")
|
||||
return hmat._APART.sub("", t)
|
||||
|
||||
|
||||
def _tidy(text: str) -> str:
|
||||
t = re.sub(r"(?<=[a-z])[.\s]+(?=[a-z])", "", text)
|
||||
for a, b in SPELL:
|
||||
t = re.sub(a, b, t)
|
||||
t = re.sub(r"\d+급", "", t)
|
||||
return re.sub(r"[^가-힣a-z0-9]", "", t)
|
||||
|
||||
|
||||
def _bag(text: str) -> tuple[float, ...]:
|
||||
t = _THOUSAND.sub("", _SQUARE.sub("", _KS.sub("", text)))
|
||||
return tuple(sorted(float(x) for x in _NUM.findall(t)))
|
||||
|
||||
|
||||
def _quals(text: str) -> frozenset[str]:
|
||||
words = (w for w in re.findall(r"[가-힣]{2,}", text) if not hmat._region(w))
|
||||
return frozenset(w for w in words if w not in hmat.SUPPLY and w not in NOISE)
|
||||
|
||||
|
||||
def _item(name: Any, spec: Any) -> dict[str, Any]:
|
||||
"""이름 · 규격 → 바탕 · 괄호 조각 · 수 묶음 · 규격 낱말 · 온 이름 글."""
|
||||
low, spec = _low(name), _low(spec)
|
||||
parens = _PAREN.findall(low)
|
||||
pieces = tuple(p for p in (_tidy(x) for x in parens for x in x.split(",")) if p)
|
||||
core = _tidy(hmat._bare(low))
|
||||
return {
|
||||
"core": core,
|
||||
"pieces": pieces,
|
||||
"chars": "".join(sorted(core + "".join(pieces))),
|
||||
"bag": _bag(f"{spec} {' '.join(parens)}"),
|
||||
"quals": _quals(f"{' '.join(pieces)} {spec}"),
|
||||
"whole": core + "".join(pieces),
|
||||
"spec": _tidy(spec),
|
||||
}
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def _book(_got: int) -> dict[str, Any]:
|
||||
rows, _ = hmat.pool()
|
||||
out: list[dict[str, Any]] = []
|
||||
by: dict[str, dict[str, list[int]]] = {
|
||||
"core": {},
|
||||
"chars": {},
|
||||
"piece": {},
|
||||
"tail": {},
|
||||
"spec": {},
|
||||
}
|
||||
for at, r in enumerate(rows):
|
||||
one = {**_item(r["one"]["이름"], r["one"]["규격"]), "row": r}
|
||||
out.append(one)
|
||||
c = one["core"]
|
||||
by["core"].setdefault(c, []).append(at)
|
||||
by["chars"].setdefault(one["chars"], []).append(at)
|
||||
by["spec"].setdefault(one["spec"], []).append(at)
|
||||
for p in one["pieces"]:
|
||||
by["piece"].setdefault(p, []).append(at)
|
||||
for i in range(1, len(c) - 1): # 끝 낱말(두 글자 이상)
|
||||
by["tail"].setdefault(c[i:], []).append(at)
|
||||
return {"줄": out, "찾기": by}
|
||||
|
||||
|
||||
def book() -> dict[str, Any]:
|
||||
return _book(id(hm.targets()))
|
||||
|
||||
|
||||
def _related(me: dict[str, Any]) -> dict[int, bool]:
|
||||
"""이름 같은 우리 줄 → {차례: 굳셈(True) · 약함(False)}."""
|
||||
by, c = book()["찾기"], me["core"]
|
||||
strong = [*by["core"].get(c, []), *by["chars"].get(me["chars"], []), *by["piece"].get(c, [])]
|
||||
if (
|
||||
c not in by["core"]
|
||||
): # 괄호 이름(「서비스볼트(고장력볼트)」) — 바탕 이름이 우리 표에 없을 때만
|
||||
strong += [i for p in me["pieces"] for i in by["core"].get(p, [])]
|
||||
weak = (
|
||||
[*by["tail"].get(c, [])] if len(c) >= 2 else []
|
||||
) # 우리 이름 끝 = 가져온 이름(「PVC지수판」)
|
||||
for k in range(
|
||||
2, len(c) - 1
|
||||
): # 우리 이름 = 가져온 이름 앞(「구역화물자동차」) · 뒤 덧말이 우리 낱말 아님
|
||||
if c[k:] not in by["core"] and c[k:] not in by["tail"]:
|
||||
weak += by["core"].get(c[:k], [])
|
||||
out = dict.fromkeys(weak, False)
|
||||
out.update(dict.fromkeys(strong, True))
|
||||
return out
|
||||
|
||||
|
||||
def _bigrams(words: frozenset[str]) -> set[str]:
|
||||
return {w[i : i + 2] for w in words for i in range(len(w) - 1)}
|
||||
|
||||
|
||||
def _qual_fit(me: dict[str, Any], x: dict[str, Any]) -> tuple[bool, bool, bool]:
|
||||
"""(부딪힘, 겹침, 낱말 있음) — 상대 온 이름에 든 낱말은 뺌."""
|
||||
a = frozenset(w for w in me["quals"] if _tidy(w) not in x["whole"])
|
||||
b = frozenset(w for w in x["quals"] if _tidy(w) not in me["whole"])
|
||||
share = bool(_bigrams(a) & _bigrams(b))
|
||||
return bool(a and b and not share), share, bool(a or b)
|
||||
|
||||
|
||||
def _near(x: float, y: float) -> bool:
|
||||
return any(abs(x * s - y) <= 1e-6 * max(abs(y), 1) or abs(y * s - x) <= 1e-6 * max(abs(x), 1) for s in SCALES) # fmt: skip
|
||||
|
||||
|
||||
def num_fit(a: tuple[float, ...], b: tuple[float, ...]) -> tuple[int, int]:
|
||||
"""(갈래 3 같음 · 2 단위 배 · 1 한쪽이 듦 · 0 다름, 맞은 수 개수)."""
|
||||
if a == b:
|
||||
return 3, len(a)
|
||||
if a and len(a) == len(b) and len(a) <= 6:
|
||||
if any(all(_near(x, y) for x, y in zip(a, p)) for p in permutations(b)):
|
||||
return 2, len(a)
|
||||
small, big = sorted((list(a), list(b)), key=len)
|
||||
if small and len(small) < len(big):
|
||||
rest = list(big)
|
||||
for x in small:
|
||||
if x not in rest:
|
||||
return 0, 0
|
||||
rest.remove(x)
|
||||
return 1, len(small)
|
||||
return 0, 0
|
||||
|
||||
|
||||
def _fold(x: dict[str, Any]) -> tuple:
|
||||
r = x["row"]
|
||||
return r["base"], r["key"][0], r["key"][2]
|
||||
|
||||
|
||||
def _scored(line: dict[str, Any]) -> tuple[dict[str, Any], list[tuple[tuple, dict[str, Any]]]]:
|
||||
me = _item(line.get("이름"), line.get("규격"))
|
||||
rows = book()["줄"]
|
||||
out = []
|
||||
for at, strong in _related(me).items():
|
||||
x = rows[at]
|
||||
clash, share, words = _qual_fit(me, x)
|
||||
if not clash:
|
||||
out.append(
|
||||
((strong, share, *num_fit(me["bag"], x["bag"]), words, x["core"] == me["core"]), x)
|
||||
)
|
||||
return me, out
|
||||
|
||||
|
||||
def _pick(group: list[dict[str, Any]], line: dict[str, Any]) -> dict[str, Any]:
|
||||
"""한 품목의 변형 줄들 → 하나(줄 관급/사급 › 줄 지역 › 서울 › 같은 단위 › 엑셀 차례 — `M02_Elem_Alias.one` 과 같은 차례)."""
|
||||
supply, regions = hmat._wants(line)
|
||||
unit = hm.mm.plain_unit(line.get("단위"))
|
||||
|
||||
def rank(r: dict[str, Any]) -> tuple:
|
||||
return (
|
||||
supply is not None and r["supply"] not in (None, supply),
|
||||
bool(regions) and not hmat._hit(r, regions),
|
||||
"서울" not in r["region"],
|
||||
r["key"][1] != unit,
|
||||
r["at"],
|
||||
)
|
||||
|
||||
return min((x["row"] for x in group), key=rank)["one"]
|
||||
|
||||
|
||||
def same(line: dict[str, Any]) -> dict[str, Any] | None:
|
||||
"""㉮ 표기만 다름 — 우리 품목 하나면 그 자재 줄(`hm.targets` 꼴) · 아니면 None."""
|
||||
me, scored = _scored(line)
|
||||
if not me["spec"]: # 가져온 규격 빔 = 규격에 든 이름(「안고초」)만 · 그 밖은 ㉯
|
||||
hits = [book()["줄"][i] for i in book()["찾기"]["spec"].get(me["whole"], [])]
|
||||
hits = [x for x in hits if not x["bag"] and not me["bag"]]
|
||||
return _pick(hits, line) if hits and len({_fold(x) for x in hits}) == 1 else None
|
||||
ok = []
|
||||
for (strong, share, tier, common, words, same_core), x in scored:
|
||||
if not tier or (tier == 1 and not strong):
|
||||
continue
|
||||
if not me["bag"] and not x["bag"] and not share and words:
|
||||
continue # 수 없는 규격 = 규격 낱말이 겹쳐야
|
||||
ok.append(((tier, common, share, strong, same_core), x))
|
||||
if not ok:
|
||||
return None
|
||||
best = max(k for k, _ in ok)
|
||||
group = [x for k, x in ok if k == best]
|
||||
return _pick(group, line) if len({_fold(x) for x in group}) == 1 else None
|
||||
|
||||
|
||||
def _stems(me: dict[str, Any]) -> set[str]:
|
||||
return {_STEM.sub("", w) for w in me["quals"]}
|
||||
|
||||
|
||||
def narrow(line: dict[str, Any], limit: int = 5) -> list[dict[str, Any]]:
|
||||
"""㉯ 이름 같은 품목 묶음(굳센 같음) → 규격 낱말 어근 품은 줄 › 수 묶음 닮음 › 덧말 적음 차례(품목마다 하나)."""
|
||||
me, scored = _scored(line)
|
||||
stems = _stems(me)
|
||||
rows = [(k, x) for k, x in scored if k[0]]
|
||||
rank = []
|
||||
for (_strong, _share, tier, common, _words, same_core), x in rows:
|
||||
text = x["whole"] + x["spec"]
|
||||
hit = sum(s in text for s in stems)
|
||||
rank.append(((-hit, -tier, -common, not same_core, len(x["spec"]), x["row"]["at"]), x))
|
||||
seen, out = set(), []
|
||||
for _k, x in sorted(rank, key=lambda t: t[0]):
|
||||
if (f := _fold(x)) not in seen:
|
||||
seen.add(f)
|
||||
out.append(_pick([y for _, y in rank if _fold(y) == f], line))
|
||||
return out[:limit]
|
||||
|
||||
|
||||
def spec_first(cands: list[dict[str, Any]], line: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""후보 차례 — 규격 수 묶음(둘 이상)이 줄과 같은 것 먼저(「중하중그레이팅 395×995×52」)."""
|
||||
mine = _item(line.get("이름"), line.get("규격"))["bag"]
|
||||
if len(mine) < 2:
|
||||
return cands
|
||||
return sorted(cands, key=lambda c: _item(c.get("이름"), c.get("규격"))["bag"] != mine)
|
||||
@@ -103,7 +103,7 @@ def _m01_picks(kind: str, name: str, spec: str, unit: str, _whole: int) -> list[
|
||||
clean, apart = hmat.clean({"이름": name, "규격": spec, "단위": unit})
|
||||
sure, _ = hmat.sure(clean)
|
||||
got = [r["one"] for r in sure[:1] if not r["one"].get("전국")]
|
||||
got = got or hmat.candidates(clean, TOP)
|
||||
got = got or _found(clean)
|
||||
more = {"별도계상": True} if apart else {}
|
||||
return [
|
||||
pick("재료", x.get("자재"), x["이름"], x.get("규격"), x.get("단위"), **more)
|
||||
@@ -113,6 +113,23 @@ def _m01_picks(kind: str, name: str, spec: str, unit: str, _whole: int) -> list[
|
||||
return []
|
||||
|
||||
|
||||
def _found(line: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""817-1 재료 — ㉮ 표기만 다름 하나 › 옛 후보 하나 › ㉯ 이름 같은 묶음 좁힘 + 규격 수 같은 후보 먼저 + 옛 후보."""
|
||||
from M02_MasterTemplete import M02_Hopyo_MatSame as ms
|
||||
|
||||
if one := ms.same(line):
|
||||
return [one]
|
||||
cands = hmat.candidates(line, 4 * TOP)
|
||||
if len({x.get("자재") for x in cands[:TOP]}) == 1:
|
||||
return cands[:TOP]
|
||||
out, seen = [], set()
|
||||
for x in [*ms.narrow(line, TOP), *ms.spec_first(cands, line)[:TOP], *cands[:TOP]]:
|
||||
if x.get("자재") not in seen:
|
||||
seen.add(x.get("자재"))
|
||||
out.append(x)
|
||||
return out
|
||||
|
||||
|
||||
def _alias(kind: str, name: str, spec: str) -> list[dict[str, Any]]:
|
||||
"""M01 별칭 칸 이음(`M02_Elem_Alias`) — 인력 = 이름 · 재료 = 이름 + 규격 › 표기 정규화 정확 일치 ·
|
||||
같은 품목 지역 줄은 하나."""
|
||||
|
||||
@@ -9,6 +9,7 @@ from __future__ import annotations
|
||||
NOTE = "참고 · " # `M02_San_Emit.NOTE` 와 같음(사람 확인 표지 머리)
|
||||
OIL_BASE = ("전국평균", "일반용(갑)Ⅰ 저압") # 유가 · 전력 기본(요구 748 · 우리 기초 기본값)
|
||||
QUOTE = "견적 — 그대로(이름 · 값) · [단가 갱신] 대상 아님" # 815-1 K5 · 요구 762-3
|
||||
APART = "별도계상 · 품목은 프로젝트에서" # 817-1 — 일위대가 길(`M02_CheckApart`)과 같은 글 · 요소 잇기 없음
|
||||
|
||||
|
||||
def m01(kind: str, src: dict) -> tuple[str | None, str | None]:
|
||||
@@ -24,6 +25,10 @@ def m01(kind: str, src: dict) -> tuple[str | None, str | None]:
|
||||
from M02_MasterTemplete import M02_Hopyo_Row as hrow
|
||||
|
||||
line = {k: src.get(k) or "" for k in ("이름", "규격", "단위")}
|
||||
if kind == "재료" and apart(
|
||||
line
|
||||
): # 817-1 — 원가 합 밖(원본 금액 그대로) · 품목은 프로젝트에서 이음
|
||||
return None, f"{NOTE}{APART}"
|
||||
if (
|
||||
kind == "기계"
|
||||
): # 812-9 — 하나 = 이음 · 아니면 표지(후보 여럿 · 못 찾음 · 조용히 넘기지 않음)
|
||||
@@ -68,6 +73,13 @@ def m01(kind: str, src: dict) -> tuple[str | None, str | None]:
|
||||
return None, None
|
||||
|
||||
|
||||
def apart(line: dict) -> bool:
|
||||
"""줄 이름 · 규격에 「별도계상」(`M02_Hopyo_Material.clean`)."""
|
||||
from M02_MasterTemplete import M02_Hopyo_Material as hmat
|
||||
|
||||
return hmat.clean(line)[1]
|
||||
|
||||
|
||||
def quote(src: dict) -> tuple[str | None, str | None]:
|
||||
"""견적 · 운임 · 수수료 꼴 줄(갈래 밖 — EST 견적 · STmate 고정 코드 · 815-1 K5) → 우리 자재에 이어지면
|
||||
(코드 · 별칭 · 정확 일치 · 단가 줄 · 우리 값 있을 때) 그 키 + 갈래 재료(우리 값 · 엑셀 값은 검산) ·
|
||||
@@ -143,11 +155,13 @@ def why(item: dict) -> str:
|
||||
from M02_MasterTemplete import M02_Hopyo_Elem as elem
|
||||
from M02_MasterTemplete import M02_Hopyo_Row as hrow
|
||||
|
||||
if apart({"이름": name, "규격": res.get("규격") or ""}):
|
||||
return APART
|
||||
if elem.oil_rows(name):
|
||||
return "유가 후보 여럿 — 고름"
|
||||
line = {"이름": name, "규격": res.get("규격") or "", "단위": res.get("단위") or ""}
|
||||
if hrow.m01_picks("재료", line):
|
||||
return "자재 후보 여럿 — 고름"
|
||||
if picks := hrow.m01_picks("재료", line): # 817-1 ㉯ 1 순위를 글에(고르기 창은 이름 찾기)
|
||||
return f"자재 후보 여럿 — 고름 · 1 순위 {picks[0]['이름']} {picks[0]['규격']}".strip()
|
||||
return "운임표 없음" if any(w in name for w in FREIGHT) else "자재 없음(우리 자재표)"
|
||||
if kind == "인력":
|
||||
return "노임 없음"
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,100 @@
|
||||
# 817-1 불러오기 재료 찾기 — 전후 전수 보고(요구 773-5 · 774 · 방향 장부 O 장)
|
||||
|
||||
- 대상 = 815 와 같은 자 — 원파일 EST 6 · STmate 7(2,220 블록) + 실무 엑셀 11(1,021 블록) · 재는 법 = 815-1 과 같음(블록마다 금액 · 요소 이음 · [우리 단가로])
|
||||
- 전 = 817-1 앞 판 · 후 = 커밋 bb243af
|
||||
- 자세한 표 = `_목록_817-1_재료찾기.xlsx`(과녁 100 줄 · 새로 이어진 줄 · 남은 못 이음 · 전후)
|
||||
- 별칭 값 · M01 마스터 자료 · storage 층 사본 안 건드림 · 인력 · 기계 · 유가 길 그대로
|
||||
|
||||
## 요약
|
||||
|
||||
| 잣대 | 전 | 후 |
|
||||
|---|---|---|
|
||||
| 재료 이음(전체) | 88.0 %(1,792 / 2,036) | **93.5 %(1,904 / 2,036)** |
|
||||
| 못 이은 재료 | 244 줄 · 100 종 | **132 줄 · 49 종** |
|
||||
| 새로 이어진 줄 | — | 112 줄 · 51 종(목록 시트 「새로 이어진 줄」) |
|
||||
| 끊긴 이음 | — | 0 |
|
||||
| 이은 키가 바뀐 줄 | — | 28 줄 — 치즐 0.7㎥ 이 엉뚱한 「치즐(0.2㎥)」 에서 「치즐(0.7㎥)」 로(바로잡음) |
|
||||
| 금액(엑셀 단가 그대로 셈) | 3,241 블록 100 % | 같음 100 %(셈 바뀐 블록 0) |
|
||||
| 검산 틀린 줄 | 929 | 929(그대로) |
|
||||
|
||||
### 원천 셋
|
||||
|
||||
| 원천 | 전후 | 인력 | 기계 | 재료 | 유가 | 형제 | 모두 우리 값 | 우리 값 + 견적 | 이어짐 · 우리 값 빔 | 엑셀 단가 남음 | 0 원 줄 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| EST 원파일(1,849) | 전 | 100 | 97.2 | 99.4 | 100 | 100 | 1,622 | 6 | 100 | 75 | 46 |
|
||||
| | 후 | 100 | 97.2 | **99.7** | 100 | 100 | 1,622 | 6 | 100 | 75 | 46 |
|
||||
| STmate 원파일(371) | 전 | 100 | 95.2 | 40.2 | — | 100 | 278 | 13 | 1 | 79 | 0 |
|
||||
| | 후 | 100 | 95.2 | **59.8** | — | 100 | **285** | 13 | 11 | **62** | 0 |
|
||||
| 실무 엑셀(1,021) | 전 | 100 | 93.2 | 54.3 | 100 | 100 | 823 | 12 | 9 | 177 | 0 |
|
||||
| | 후 | 100 | 93.2 | **78.2** | 100 | 100 | **854** | 12 | 27 | **128** | 0 |
|
||||
|
||||
- 「이어짐 · 우리 값 빔」 = 우리 자재 줄에 이었는데 그 줄 값 칸이 빈 블록(요구 776 — 흠 아님 · 자재표 보강 몫) · 늘어난 28 블록 = 모두 치즐 0.7㎥(값 칸 빔)
|
||||
- 블록 옮김: 엑셀 단가 남음 → 모두 우리 값 43(STmate 7 · 엑셀 36) · 엑셀 단가 남음 → 이어짐 · 우리 값 빔 23 · 모두 우리 값 → 이어짐 · 우리 값 빔 5(치즐 0.2 → 0.7 바로잡음)
|
||||
|
||||
## 엔진 — 새 단 셋(`M02_Hopyo_MatSame` · 재료 길 = 별칭 › 정확 일치 › 바탕 이름 유일 짝 › ㉮ › ㉯)
|
||||
|
||||
- ㉮ 이음(자동) = 이름이 같은 품목 + 규격 수 묶음 같음 + 규격 낱말 안 부딪힘 + 그런 우리 품목 하나(지역 · 관급/사급 줄은 한 품목)
|
||||
- 이름 같음: 빈칸 · 괄호 · 지역 · 관급/사급 꼬리 · 등급(1급) 뗌 · 점 · 빈칸 영문(P.V.C) · 소리 표기 넷(칠렌 → 틸렌 · 매쉬 → 메쉬 · 신나 → 신너 · pipe → 파이프) · 괄호 차례(유로폼(패널) = 유로폼패널) · 괄호 이름(서비스볼트(고장력볼트) → 고장력볼트 — 가져온 바탕 이름이 우리 표에 없을 때만) · 규격에 든 이름(안고초 → 산림종자 안고초)
|
||||
- 약한 같음(수 묶음 같음 · 단위 배일 때만): 우리 이름 끝 = 가져온 이름(지수판 → PVC지수판 · 고무테이프 → 절연 면고무테이프 · 철물 직교자재 → 조임철물) · 우리 이름 = 가져온 이름 앞(구역화물자동차 → 구역화물 · 뒤 덧말이 우리 낱말이 아닐 때만)
|
||||
- 수 묶음: 차례 무관 · KS 번호 · 제곱 꼬리 뗌 · 천 단위 쉼표 · 같음 › 낱마다 단위 배(3.6×3.0×3.0 = 30×30×3600) › 한쪽이 다른 쪽에 듦(측구수로관 300×300×2000×120 → 300×300×120 · 이름이 굳세게 같을 때만)
|
||||
- 낱말 부딪힘 = 두 쪽 다 괄호 · 규격 한글 낱말이 있고 두 글자 묶음이 안 겹침(연강용 ↔ 주철용 · 표준 ↔ 단부구간 · 스티로폼 ↔ ABS코팅폼)
|
||||
- ㉯ 후보 좁힘 = 이름 같은 묶음에서 규격 어근(초본류 → 초본) 품은 줄 › 수 묶음 닮음 › 바탕 같음 › 덧말 적은 줄 차례 + 옛 후보 가운데 규격 수 묶음(둘 이상) 같은 줄 먼저 · 자동 이음 아님
|
||||
- 화면: 못 이은 재료 까닭 글에 1 순위(「자재 후보 여럿 — 고름 · 1 순위 종 자 초본위주형」) — [이음 고르기] 창은 이름 찾기 그대로
|
||||
- 사전 = 소리 · 영문 넷 · 덧글 낱말 다섯(이내 · 이하 · 이상 · 국산 · 포함) 한 곳 · 자재마다 박은 예외 없음
|
||||
|
||||
## 과녁 100 줄(브레인 과녁을 우리 자재표로 다시 확인)
|
||||
|
||||
| 과녁 | 줄 | 같음 | 다름 |
|
||||
|---|---|---|---|
|
||||
| 이음 | 65 | 49 | 16 — 1 순위는 맞음 10 · 그 밖 6 |
|
||||
| 후보 1 순위(종자) | 5 | 5 | 0 |
|
||||
| 후보(사람이 고름) | 26 | 25 | 1 |
|
||||
| 잇지 않음 | 4 | 4 | 0 |
|
||||
|
||||
과녁과 다른 줄(맞추지 않음 · 까닭):
|
||||
|
||||
| 행 | 가져온 줄 | 과녁 | 결과 | 까닭 |
|
||||
|---|---|---|---|---|
|
||||
| 3 · 7 | 각재 외송 · 각재 (외송) | 이음 | ㉯ 1 순위 각재(외송) | 규격 수 없음 · 빔 → 수 묶음 다름 |
|
||||
| 4 | 요소 | 이음 | ㉯ 1 순위 요소 N:46% | 가져온 규격 빔 |
|
||||
| 31 | 유압식드릴스리브 T38×190mm | 이음 | 후보 1 순위 유압드릴스리브 | 「유압식」 ↔ 「유압」 이름 다름 |
|
||||
| 40 | 안전표지판 삼각,D=900 | 이음 | ㉯ 1 순위 교통안전표지판 900,삼각 | 같은 수 · 낱말 줄 둘(LED 교통안전표지판(태양광) 900,삼각) |
|
||||
| 47 | 강관써포트 V4 2600-4000mm | 이음 | 후보 1 순위 강관동바리 | 이름 다름 |
|
||||
| 55 | 아세틸렌 용접용 | 이음 | ㉯ 1 순위 아세틸렌 98%(용접용) | 규격 수 없음 ↔ 98% |
|
||||
| 90 · 92 | 중하중그레이팅 395×995×52 · 595×995×67 | 이음 | 후보 1 순위 스틸그레이팅(중하중용) 같은 치수 | 이름 다름 — 같은 수 묶음 후보 먼저(전에는 다른 치수 OK그레이팅이 1 순위) |
|
||||
| 98 | 공사안전PE드럼 500×800 | 이음 | 후보 1 순위 PE드럼(일체형) Φ500×800 | 가져온 이름 앞 덧말은 안 뗌(PVC용접봉 ↔ 용접봉 틀린 이음 막음) |
|
||||
| 67 | 와이어매쉬 #6 100×100 | 이음 MT007130(관급) | 이음 MT007136(서울) | 같은 품목 변형 — 별칭 · 정확 일치 길과 같은 차례(서울 먼저) |
|
||||
| 54 | 다이아몬드톱날 Ø14"×3.2t | 이음 블레이드 | 후보(1 순위 다름) | 이름 다름 — 별칭 몫 |
|
||||
| 79 · 81 | 참싸리(박피) · 참싸리 박피 | 이음 산림종자 참싸리 | 못 찾음 | 박피 ↔ 풍화암,강마사토 덧말 부딪힘 — 별칭 몫 |
|
||||
| 83 | 퇴비(천연부엽토) 20㎏/포 | 이음 천연부엽토 | 후보(1 순위 퇴비 1등급) | 「퇴비」가 우리 표에 따로 있어 괄호 이름 길 안 씀(보조강재(열연강판) → 열연강판 같은 이음 막음) |
|
||||
| 95 | 삼각표지판 삼각,900mm | 이음 | 후보(1 순위 다름) | 이름 다름 — 별칭 몫 |
|
||||
| 93 | 잔디 0.18×0.18×0.01 | 후보 | **이음 천연잔디(한국형) 0.18*0.18*0.01** | 우리 표에 수 묶음 같은 줄 하나 — 과녁은 잔디 0.03 줄만 봄 · 브레인 확인 필요 |
|
||||
|
||||
- 「가까운 줄 제안」 바로잡음: 신나 → 신너(조합페인트용) KSM-6060(2종) ㉮(전 = 신나무 1 순위) · 중하중그레이팅 → 같은 치수 1 순위
|
||||
- 틀린 이음 막는 견본(시험): 용접봉(연강용) ↔ 주철용 · PVC용접봉 ↔ 용접봉 · 어닐링철선 #6 ↔ #8 · 잔디 0.01 ↔ 0.03 · 표지판지주 3.5M ↔ 3000 · 강관비계 6m ↔ 4.0m · 신나 ↔ 신나무 — 모두 안 이음
|
||||
|
||||
## 새로 이어진 줄(51 종 · 112 줄) — 틀린 이음 0
|
||||
|
||||
- 전부 목록 시트(가져온 이름 · 규격 → 우리 키 · 이름 · 규격 · 맞은 까닭) · 한 줄씩 읽음 — 과녁과 다른 이음은 93 잔디(위 표) · 67 와이어매쉬 변형뿐
|
||||
- 과녁 밖에서 새로 이어진 것 = 0(새 이음 51 종 모두 과녁 100 줄 안)
|
||||
|
||||
## 별도계상
|
||||
|
||||
- 단가산출 불러오기 길(엑셀 산근 · 원파일): 전에는 표지 없이 「자재 후보 여럿」 못 이음 → 이제 요소 잇기 없음 + 표지 「참고 · 별도계상 · 품목은 프로젝트에서」(일위대가 호표 길 `M02_CheckApart` 와 같은 글) · 못 이음 띠 「파형강관(별도계상) (별도계상 · 품목은 프로젝트에서) · 이음 고르기」
|
||||
- 원가 합 밖 = 그대로(줄 값 0 · 원본 금액 그대로) · 0 원 줄 수에 안 듦(엑셀 값 0 줄) · 대상 = 동부청영월 파형강관 · 이형철근 8 줄(엑셀 · STmate)
|
||||
- 일위대가 호표 길 · 프로젝트로 복사(`M02_Doc_Copy` 합밖) 은 전부터 되어 있음
|
||||
|
||||
## 화면(8001 · 5174 · 일반 계정 개인 층 · 「sub1임시_동부청영월.xlsx」 · 「sub1임시_울진소광.xlsx」)
|
||||
|
||||
- 단산58 유로폼 설치 · 해체 — 「유로폼(패널) 600*1200」 줄 → MT000467 유로폼패널(저장된 SK 이음 키로 확인) · 이음 없는 자원 0
|
||||
- 단산71 파형강관부설 — 「파형강관(별도계상) (별도계상 · 품목은 프로젝트에서)」 · 금액 15,898 그대로
|
||||
- 단산1492 씨뿌리기(울진소광) — 「종자(초본류) … 1 순위 종 자 초본위주형」 · 「종자(목본류) … 1 순위 종 자 목본군락형」(ORCA 탭 사진 `tmp/q17/ui/orca_1492.png`)
|
||||
- 밝음 · 어두움 · 1366×768 가로 넘침 0 · 콘솔 오류 0(로그인 전 세션 401 하나 뺌) · 만든 SK 셋 · 올린 HP 둘 지움
|
||||
|
||||
## 남은 것
|
||||
|
||||
| # | 내용 | 갈래 |
|
||||
|---|---|---|
|
||||
| 1 | 일위대가 호표 화면(일위대가표 시트) 줄 판정은 다른 길(`M02_Hopyo_Units`) — 안고초 · 참싸리 등 아직 분홍(못 이음) | 이번 고침 밖 |
|
||||
| 2 | 남은 못 이은 재료 132 줄 · 49 종 = 이름 다름 · 규격 다름 · 우리 표에 없음 → 별칭 몫(목록 시트) | 사용자 별칭 |
|
||||
| 3 | 93 잔디 0.01 → 천연잔디(한국형) 0.01 이음 맞는지 | 브레인 확인 |
|
||||
@@ -0,0 +1,192 @@
|
||||
"""817-1 불러오기 재료 찾기 — 표기만 다름 ㉮ 이음 · ㉯ 후보 좁힘 · 틀린 이음 막기 · 별도계상 표지(요구 773-5 · 774).
|
||||
|
||||
과녁 = 사용자 시트 「수동연결(자재)」 100 줄(브레인 과녁 `_과녁_773_자재표기.tsv` 를 우리 자재표로 다시 확인한 결과).
|
||||
「후보」 줄 키 = 1 순위를 굳힌 줄만(종자 · 그레이팅 · 표기만 다름인데 수 묶음 · 이름이 안 맞아 ㉯ 로 둔 줄) · None = 이음만 안 됨.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path[:0] = [str(ROOT), str(ROOT / "resources" / "master_data" / "scripts")]
|
||||
|
||||
from M02_MasterTemplete import M02_Hopyo_MatSame as ms # noqa: E402
|
||||
from M02_MasterTemplete import M02_Hopyo_Row as hrow # noqa: E402
|
||||
from M02_MasterTemplete import M02_San_Link as sl # noqa: E402
|
||||
|
||||
TABLE = [
|
||||
("접착제", "", "후보", None), # 1
|
||||
("패널(유로폼)", "600*1200mm", "이음", "MT000467"), # 2
|
||||
("각재", "외송", "후보", "MT000393"), # 3
|
||||
("요소", "", "후보", "MT006763"), # 4
|
||||
("내부코너패널(유로폼)", "(100x200)x1200mm", "이음", "MT000470"), # 5
|
||||
("결속선(철선)", "#20 , 0.9M/M", "후보", None), # 6
|
||||
("각재 (외송)", "", "후보", "MT000393"), # 7
|
||||
("파형강관", "별도계상", "후보", None), # 8
|
||||
("금강사", "", "없음", None), # 9
|
||||
("고무테이프", "0.5mm×19mm×10m", "이음", "MT009202"), # 10
|
||||
("낙석방지책", "H2500×W2000(표준)", "후보", None), # 11
|
||||
("낙석방지책", "H2500×W2000(단부)", "후보", None), # 12
|
||||
("종자", "초본류", "후보", "MT006646"), # 13
|
||||
("종자", "목본류", "후보", "MT006648"), # 14
|
||||
("어닐링철선(결속선)", "#20 0.9mm 200.4m/kg", "이음", "MT000209"), # 15
|
||||
("내수합판1급", "12mmx1220x2440", "이음", "MT000018"), # 16
|
||||
("어닐링철선(결속선)", "#20 0.9M/M", "이음", "MT000209"), # 17
|
||||
("유로폼(패널)", "600*1200", "이음", "MT000467"), # 18
|
||||
("내부코너패널", "(200+200)*1200", "이음", "MT000472"), # 19
|
||||
("합판(내수)", "12×1220×2440", "이음", "MT000018"), # 20
|
||||
("조임철물", "(직교자재)", "이음", "MT000499"), # 21
|
||||
("에폭시수지", "YD-128", "후보", None), # 22
|
||||
("종자", "생태복원형,초본류", "후보", "MT006646"), # 23
|
||||
("종자", "생태복원형,목본류", "후보", "MT006648"), # 24
|
||||
("내부코너패널(유로폼)", "(100+200)*1200mm", "이음", "MT000470"), # 25
|
||||
("유로폼(패널)", "600×1200", "이음", "MT000467"), # 26
|
||||
("콘테이너하우스", "창고용(3.0m×6.0m×2.6m)", "이음", "MT007111"), # 27
|
||||
("전기뇌관(MS)", "3.5m×500개(하이데토)", "이음", "MT005677"), # 28
|
||||
("BUTTON BIT", "Ø38×76㎜", "후보", None), # 29
|
||||
("유압드릴샹크", "T38×CM470", "후보", None), # 30
|
||||
("유압식드릴스리브", "T38×190mm", "후보", "MT005687"), # 31
|
||||
("미송(판재)", "", "후보", None), # 32
|
||||
("산림종자", "참싸리(박피)", "이음", "MT006664"), # 33
|
||||
("유기질퇴비", "", "후보", None), # 34
|
||||
("PVC지수판", "200x5T KSM3805", "이음", "MT006544"), # 35
|
||||
("PVC용접봉", "Ø3.2 (국산)", "후보", None), # 36
|
||||
("PVC파이프(일반용VG2)", "50㎜ × 4m", "이음", "MT002926"), # 37
|
||||
("혼합골재", "영월", "후보", None), # 38
|
||||
("이형철근", "별도계상", "후보", None), # 39
|
||||
("안전표지판", "삼각,D=900", "후보", "MT003202"), # 40
|
||||
("표지판지주", "D=76.3mm, 3.5M", "후보", None), # 41
|
||||
("블레이드(커터)", 'd=320 400mm 14"xt3.2', "이음", "MT005749"), # 42
|
||||
("떼", "", "없음", None), # 43
|
||||
("강관비계", "φ48.6m/m×2.3mm", "이음", "MT000481"), # 44
|
||||
("철물", "", "후보", None), # 45
|
||||
("각재(말뚝)", "Φ60", "후보", None), # 46
|
||||
("강관써포트", "V4 2600-4000mm 14.3kg", "후보", None), # 47
|
||||
("안고초", "", "이음", "MT006667"), # 48
|
||||
("비수리", "", "이음", "MT006665"), # 49
|
||||
("내수합판1급", "12m/m 1220×2440", "이음", "MT000018"), # 50
|
||||
("각재(외송)", "3.6×3.0×3.0", "이음", "MT000393"), # 51
|
||||
("내수합판1급", "12mm 1220×2440", "이음", "MT000018"), # 52
|
||||
("각재(거푸집용,외송)", "3.6×3.0×3.0", "이음", "MT000393"), # 53
|
||||
("다이아몬드톱날", 'Ø14"×3.2t(콘크리트용)', "후보", None), # 54
|
||||
("아세틸렌", "용접용", "후보", "MT003163"), # 55
|
||||
("산소", "고압가스", "이음", "MT003166"), # 56
|
||||
("신나", "KSM6060 2종(조합페인트류)", "이음", "MT003398"), # 57
|
||||
("초류종자(국내산)", "참싸리,비수리,낭아초등", "후보", None), # 58
|
||||
("종 자", "초본형", "후보", "MT006646"), # 59
|
||||
("소나무우드칩(멀칭재)", "", "후보", None), # 60
|
||||
("3단코어비트", '5"(125mm)', "후보", None), # 61
|
||||
("P V C PIPE", "Φ50M/M,VG1", "이음", "MT002861"), # 62
|
||||
("콘테이너(창 고)", "3×6×2.6", "이음", "MT007111"), # 63
|
||||
("강관비계", "48.6mm×2.3mm×6m", "후보", None), # 64
|
||||
("철물", "직교자재", "이음", "MT000499"), # 65
|
||||
("문양거푸집(ABS코팅품)", "600×1200", "이음", "MT000453"), # 66
|
||||
("와이어매쉬", "#6 100×100", "이음", "MT007136"), # 67
|
||||
("지수판", "T5×200", "이음", "MT006544"), # 68
|
||||
("용접봉(연강용)", "3.2m/m", "후보", None), # 69
|
||||
("어닐링철선 (결속선)", "#6 4.0mm 10.1m/kg", "후보", None), # 70
|
||||
("구역화물자동차(4.5Ton)", "80km이내", "이음", "MT023493"), # 71
|
||||
("구역화물자동차(4.5Ton)", "60km이내", "이음", "MT023491"), # 72
|
||||
("산소(6000ℓ)", "99%,120kg/㎠", "이음", "MT003165"), # 73
|
||||
("아세칠렌", "AC 98%,용접용", "이음", "MT003163"), # 74
|
||||
("서비스볼트(고장력볼트)", "M20×60", "이음", "MT002536"), # 75
|
||||
("보조강재(열연강판)", "T=9-12", "후보", None), # 76
|
||||
("구역화물자동차(8Ton)", "10km이내", "이음", "MT023654"), # 77
|
||||
("구역화물자동차(8Ton)", "20km이내", "이음", "MT023655"), # 78
|
||||
("참싸리(박피)", "", "없음", None), # 79
|
||||
("부산물비료", "20kg/포", "후보", None), # 80
|
||||
("참싸리", "박피", "없음", None), # 81
|
||||
("복합비료", "21-17-17 20kg/포", "이음", "MT006490"), # 82
|
||||
("퇴비(천연부엽토)", "20㎏/포", "후보", None), # 83
|
||||
("문양거푸집(ABS코팅폼)", "600×1200×20~30mm", "이음", "MT000453"), # 84
|
||||
("P.V.C PIPE", "VG1(50mm)", "이음", "MT002861"), # 85
|
||||
("다웰바(DOWEL BAR)", "Ø32×1,000", "이음", "MT006540"), # 86
|
||||
("P.V.C PIPE", "VG1(35mm)", "이음", "MT002859"), # 87
|
||||
("휠라", "T=15mm,1000×1000mm", "없음", None), # 88
|
||||
("측구수로관", "300×300×2000×120,앵글포함", "이음", "MT015883"), # 89
|
||||
("중하중그레이팅", "395×995×52", "후보", "MT007025"), # 90
|
||||
("측구수로관", "500×500×2000×150,앵글포함", "이음", "MT015887"), # 91
|
||||
("중하중그레이팅", "595×995×67", "후보", "MT007032"), # 92
|
||||
("잔디", "0.18×0.18×0.01", "이음", "MT006796"), # 93
|
||||
("강관비계", "4000×48.6mm×2.3mm", "이음", "MT000481"), # 94
|
||||
("삼각표지판", "삼각,900mm", "후보", None), # 95
|
||||
("표지판지주", "D=76.3mm×3.0m", "이음", "MT003241"), # 96
|
||||
("공사안내표지판", "300×1500", "후보", None), # 97
|
||||
("공사안전PE드럼", "500×800", "후보", "MT003311"), # 98
|
||||
("윙카호스", "10cm", "후보", None), # 99
|
||||
("조합페인트", "KSM6020 2급,백색", "이음", "MT003372"), # 100
|
||||
]
|
||||
|
||||
|
||||
def _link(name, spec):
|
||||
key, mark = sl.m01("재료", {"이름": name, "규격": spec, "단위": "", "갈래": "재료"})
|
||||
return key, mark, hrow.m01_picks("재료", {"이름": name, "규격": spec, "단위": ""})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name,spec,want,key", TABLE)
|
||||
def test_target_rows(name, spec, want, key):
|
||||
got, _mark, picks = _link(name, spec)
|
||||
if want == "이음":
|
||||
assert got == key
|
||||
else:
|
||||
assert got is None
|
||||
assert bool(picks) == (want == "후보")
|
||||
if key:
|
||||
assert picks[0]["키"] == key
|
||||
|
||||
|
||||
def test_counts():
|
||||
linked = sum(1 for *_x, w, _k in TABLE if w == "이음")
|
||||
assert linked == 51
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name,spec,bad",
|
||||
[
|
||||
("용접봉(연강용)", "3.2m/m", "MT003195"), # 연강용 ↔ 주철용
|
||||
("PVC용접봉", "Ø3.2 (국산)", "MT003195"), # PVC 용접봉 ↔ 용접봉
|
||||
("어닐링철선 (결속선)", "#6 4.0mm 10.1m/kg", "MT000199"), # #6 ↔ #8
|
||||
("잔디", "0.18×0.18×0.01", "MT019017"), # 0.01 ↔ 0.03
|
||||
("표지판지주", "D=76.3mm, 3.5M", "MT003241"), # 3.5M ↔ 3000
|
||||
("신나", "KSM6060 2종(조합페인트류)", "MT018835"), # 신나 ↔ 신나무
|
||||
("강관비계", "48.6mm×2.3mm×6m", "MT000481"), # 6m ↔ 4.0m
|
||||
],
|
||||
)
|
||||
def test_no_wrong_link(name, spec, bad):
|
||||
got, _mark, picks = _link(name, spec)
|
||||
assert got != bad
|
||||
if name == "신나":
|
||||
assert got == "MT003398" # 신너(조합페인트용)
|
||||
|
||||
|
||||
def test_seed_narrow():
|
||||
assert [x["자재"] for x in ms.narrow({"이름": "종자", "규격": "초본류"})][:3] == [
|
||||
"MT006646",
|
||||
"MT006647",
|
||||
"MT006649",
|
||||
]
|
||||
|
||||
|
||||
def test_spec_forms():
|
||||
assert ms.num_fit(ms._bag("3.6×3.0×3.0"), ms._bag("30×30×3600mm"))[0] == 2
|
||||
assert ms.num_fit(ms._bag("T5×200"), ms._bag("200×5T"))[0] == 3
|
||||
assert ms.num_fit(ms._bag("Ø32×1,000"), ms._bag("Φ32*1000"))[0] == 3
|
||||
assert ms._item("PVC지수판", "200x5T KSM3805")["bag"] == ms._item("PVC지수판", "200×5T")["bag"]
|
||||
assert ms._item("유로폼(패널)", "")["chars"] == ms._item("유로폼패널", "")["chars"]
|
||||
assert ms._item("P.V.C PIPE", "")["core"] == ms._item("PVC파이프(일반관),서울", "")["core"]
|
||||
assert ms._item("아세칠렌", "")["core"] == "아세틸렌"
|
||||
|
||||
|
||||
def test_apart_mark():
|
||||
key, mark, _ = _link("파형강관", "별도계상")
|
||||
assert key is None and mark == f"{sl.NOTE}{sl.APART}"
|
||||
assert (
|
||||
sl.why({"자원": {"갈래": "재료", "이름": "철근(별도계상)", "규격": "별도계상"}}) == sl.APART
|
||||
)
|
||||
|
||||
|
||||
def test_why_shows_first():
|
||||
got = sl.why({"자원": {"갈래": "재료", "이름": "종자", "규격": "초본류"}})
|
||||
assert got == "자재 후보 여럿 — 고름 · 1 순위 종 자 초본위주형"
|
||||
Reference in New Issue
Block a user