feat(m01): 로직 재료 줄 키 → 기본 검색어 바꿔 넣기 (PLAN 8-3)
- 재료 줄 591 → {구분, 상세구분, 검색어} · 재료 입력 키 목록 76 걷음 · 넘기던 인자 뗌
- 결속선 등 원문대로 고른 품목(어닐링철선)이 검색 첫째 그대로
- 1,354 자동값 금액 변화 0 · 계산전부 멈춤 0 · check_master 로직 0 · 단위 16 그대로
- 못 맞춘 26줄은 옛 대표 · 조건 그대로: 값 빈 자리표 MT0306… 14 · 같은 단위 후보 0 빈 줄 12
- 도구: 빈 줄은 안 바꿈 · 판정은 서버 찾기(master_pick.search) · 시험 test_m01_search_words
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1MKKZKpUHTPKb513FneU8
This commit is contained in:
@@ -26,61 +26,25 @@ import check_master as cm # noqa: E402
|
||||
import master_auto as mau # noqa: E402
|
||||
import master_formula as mf # noqa: E402
|
||||
import master_material as mm # noqa: E402
|
||||
import master_pick as mp # noqa: E402
|
||||
|
||||
MASTER = HERE.parent
|
||||
KEY = re.compile(r"MT\d{6}")
|
||||
# 같은 성격 단위(8-1 단위 환산) — `plain_unit` 뒤 모양
|
||||
KINDS = ({"g", "kg", "톤"}, {"mm", "cm", "m", "km"}, {"cm2", "m2"}, {"cm3", "ℓ", "m3"})
|
||||
# 이름 끝 지역 낱말(「합판(내수), 서울」) — 자동값 차례의 지역 자리
|
||||
REGIONS = set(
|
||||
"서울 부산 대구 인천 광주 대전 울산 세종 경기 강원 충북 충남 전북 전남 경북 경남 제주 "
|
||||
"청주 전주 포항 창원 강릉 춘천 충주 고양 수원 원주 진주 목포 여수 순천 안동 구미 경주 "
|
||||
"천안 군산 익산 김해 마산 성남 안양 부천 의정부".split()
|
||||
)
|
||||
|
||||
|
||||
# ── 8-1 찾기 규칙 (마른 돌리기 판정) ─────────────────────────────────────
|
||||
def same_kind(a: str, b: str) -> bool:
|
||||
a, b = mm.plain_unit(a), mm.plain_unit(b)
|
||||
return a == b or any(a in k and b in k for k in KINDS)
|
||||
|
||||
|
||||
# ── 판정 — 서버 찾기(`master_pick.search`) 그대로 ─────────────────────────
|
||||
def _has(word: str, row: dict) -> bool:
|
||||
return mm.plain(word) in mm.plain(row.get("이름")) or mm.plain_spec(word) in mm.plain_spec(
|
||||
row.get("규격")
|
||||
)
|
||||
"""`master_pick.search` 와 같은 맞대기 — 낱말이 이름 또는 규격에 듦."""
|
||||
w = mm.plain_spec(word)
|
||||
return w in mm.plain(row.get("이름")).replace("×", "x") or w in mm.plain_spec(row.get("규격"))
|
||||
|
||||
|
||||
def found(master: mf.Master, elem: dict, unit: str | None, soft: bool = True) -> list[str]:
|
||||
"""검색어로 잡히는 자재품목 키 — 자동값 차례(값 있음 → 같은 단위 → 지역 낱말 → 키).
|
||||
soft = 지역 낱말은 거르지 않고 차례만(아니면 여느 낱말처럼 거름)."""
|
||||
words = str(elem.get("검색어") or "").split()
|
||||
region = [w for w in words if soft and w in REGIONS]
|
||||
must = [w for w in words if w not in region]
|
||||
pool = mm.by_class(master).get(elem["구분"], {}) if elem.get("구분") else master.index["MT"]
|
||||
detail = elem.get("상세구분")
|
||||
hits = [
|
||||
key
|
||||
for key, row in pool.items()
|
||||
if (not detail or row.get("상세구분") == detail)
|
||||
and all(_has(w, row) for w in must)
|
||||
and (not unit or same_kind(row.get("단위"), unit))
|
||||
]
|
||||
|
||||
def rank(key):
|
||||
row = master.get(key)
|
||||
return (
|
||||
not mm._offers(master, key),
|
||||
bool(unit) and mm.plain_unit(row.get("단위")) != mm.plain_unit(unit),
|
||||
-sum(_has(w, row) for w in region),
|
||||
key,
|
||||
)
|
||||
|
||||
return sorted(hits, key=rank)
|
||||
def found(master: mf.Master, elem: dict, unit: str | None, env: dict) -> list[str]:
|
||||
return mp.search(master, elem, env, unit)
|
||||
|
||||
|
||||
def first(master, elem, unit, soft=True) -> str | None:
|
||||
got = found(master, elem, unit, soft)
|
||||
def first(master, elem, unit, env) -> str | None:
|
||||
got = found(master, elem, unit, env)
|
||||
return got[0] if got else None
|
||||
|
||||
|
||||
@@ -160,14 +124,14 @@ def _tokens(row: dict) -> list[str]:
|
||||
|
||||
def _place(row: dict) -> str | None:
|
||||
tail = _name(row.get("이름")).split(",")[-1]
|
||||
return tail if tail in REGIONS else None
|
||||
return tail if tail in mp.REGIONS else None
|
||||
|
||||
|
||||
def _ok(master, elem, unit, cases) -> bool:
|
||||
"""판정 — 입력 벌마다 지역 낱말을 거르든(hard) 차례로만 보든(soft) 첫째가 그 품목."""
|
||||
"""판정 — 입력 벌마다 서버 찾기 첫째가 지금 품목."""
|
||||
for env, want in cases:
|
||||
filled = {k: mf.fill(v, env) if isinstance(v, str) else v for k, v in elem.items()}
|
||||
if any(first(master, filled, unit, soft) != want for soft in (True, False)):
|
||||
if first(master, filled, unit, env) != want:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -182,10 +146,9 @@ def words_for(master, base: dict, words: list[str], unit, cases) -> list[str] |
|
||||
if len(target_rows) != 1 or None in target_rows:
|
||||
return None # 입력 따라 품목이 갈리는 줄 — 끼운 낱말로만
|
||||
want = master.get(next(iter(target_rows)))
|
||||
ahead = []
|
||||
for soft in (True, False):
|
||||
got = found(master, {**elem}, unit, soft)
|
||||
ahead += got[: got.index(want["키"])] if want["키"] in got else []
|
||||
env = cases[0][0]
|
||||
got = found(master, {k: mf.fill(v, env) for k, v in elem.items()}, unit, env)
|
||||
ahead = got[: got.index(want["키"])] if want["키"] in got else got
|
||||
pick = None
|
||||
for w in _tokens(want):
|
||||
if w in words or not _has(w, want):
|
||||
@@ -209,11 +172,11 @@ def convert(master: mf.Master, row: dict, item: dict) -> tuple[dict | None, list
|
||||
# 이미 좁혀 둔 이름·규격 낱말은 그대로 검색어로(`{관경}` 같은 끼운 칸도)
|
||||
words = [w for k in ("이름", "규격") for w in str(elem.get(k) or "").split()]
|
||||
targets = {want for _, want in cases}
|
||||
if targets == {None}: # 지금 후보 0 인 줄 — 줄 이름 핵심으로 · 여전히 0 이어야 함
|
||||
root = _root(item.get("이름"))
|
||||
if root and not any(mm.plain(root) in mm.plain(w) for w in words):
|
||||
words.insert(0, root)
|
||||
elif len(targets) == 1:
|
||||
if targets == {None}:
|
||||
# 지금 같은 단위 후보 0 인 빈 줄 — 검색어는 다른 단위 품목을 잡아 값이 생기거나(금액 바뀜)
|
||||
# 후보 0 검사에 걸림 · 조건 그대로 두고 목록으로 알림
|
||||
return None, cases
|
||||
if len(targets) == 1:
|
||||
want = master.get(next(iter(targets)))
|
||||
name, *spec = _core(want)
|
||||
if not any(mm.plain(w) in mm.plain(name) for w in words):
|
||||
|
||||
Reference in New Issue
Block a user