feat(master_data): 벤치플륨관 · 소형고압블록 · 무게수로관 조달청 품명으로 짝 — 관급 못 채움 495 → 355
- 벤치플륨관 = 조달청 「벤치플룸관」 (「플룸」 조회 2,640줄 받음) · 모델 · 가로×세로 · 종별로 짝 - 소형고압블록 = 보차도용콘크리트블록 · 형 · 두께 · 색으로 짝 · 무게수로관 = 모델명 · 치수 글자 일치 - 「전지역(제주 제외)」 줄과 「대구경북」 식 붙여 쓴 두 도 지역 읽음 - 설계 문서 대조표 (채움 4,197 · 맞음 3,461) · 시험 16건 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
@@ -78,6 +78,7 @@ JOBS = [ # 분류명은 부분 글자 조회 — 「철근」은 암거블록
|
||||
(THPTY, "부직포"),
|
||||
(MAS, "경질폴리염화비닐관"),
|
||||
(THPTY, "경질폴리염화비닐관"),
|
||||
(MAS, "플룸"), # 벤치플룸관 — 분류 「배수로」 조회에는 안 나옴
|
||||
]
|
||||
# 보관하는 칸 — 나머지(첨부·원산지·이미지·긴 설명)는 버림(원본이 수백 MB)
|
||||
KEEP = (
|
||||
|
||||
@@ -18,7 +18,8 @@ FAMILIES = (
|
||||
("조경석", r"^조경석", ["MASCntrct_조경석"]),
|
||||
("골재", r"^도로용혼합골재", ["MASCntrct_골재"]),
|
||||
("시멘트", r"^시멘트", ["MASCntrct_시멘트"]),
|
||||
("수로관", r"^측구수로관", ["MASCntrct_배수로", "ThptyUcntrct_배수로"]),
|
||||
("수로관", r"^(측구수로관|무게수로관-)", ["MASCntrct_배수로", "ThptyUcntrct_배수로"]),
|
||||
("벤치플룸", r"^벤치플륨관(?!뚜껑)", ["MASCntrct_플룸"]), # 조달청 표기 「벤치플룸」
|
||||
(
|
||||
"블록",
|
||||
r"^(소형고압블록|생태옹벽블록|생태어소블록)",
|
||||
@@ -41,16 +42,11 @@ FAMILIES = (
|
||||
# 조달청에 그 품목·규격이 없거나 다산 규격으로는 물건을 가릴 수 없는 것 — 「관급」 못 채움의 까닭 글
|
||||
ABSENT = (
|
||||
(
|
||||
r"^벤치플륨",
|
||||
"조달청 종합쇼핑몰에 없음 — 벤치플륨 품명 없음(수로관 유사품이 같은 물건인지 확인 못 함)",
|
||||
r"^(벤치플륨관뚜껑|무게수로관뚜껑)",
|
||||
"짝 못 지음 — 조달청 뚜껑은 (부품) 치수 표기가 달라 다산 규격과 안 맞음",
|
||||
),
|
||||
(r"^무게수로관", "조달청 종합쇼핑몰에 없음 — 무게수로관 품명 없음"),
|
||||
(r"^HI-VP", "조달청 종합쇼핑몰에 없음 — 삼중벽 품명 없음(단층 HIVG 만 있음)"),
|
||||
(r"^KP 접합부속", "조달청 종합쇼핑몰에 없음 — 접합부속 세트 품명 없음"),
|
||||
(
|
||||
r"^소형고압블록",
|
||||
"조달청 종합쇼핑몰에 없음 — 소형고압블록 품명 없음(보차도용콘크리트블록은 형·색 표기가 달라 안 지음)",
|
||||
),
|
||||
(
|
||||
r"^(PE직관\(수도용\)|PE이중벽)",
|
||||
"짝 못 지음 — 조달청은 압력·강성 등급별 값인데 다산 규격에 등급 없음",
|
||||
@@ -110,9 +106,32 @@ def tail_of(name: str) -> str | None:
|
||||
return "제주" if ", 제주" in name else None
|
||||
|
||||
|
||||
SHAPE = {
|
||||
"I": "200x100",
|
||||
"U": "220x110",
|
||||
} # 소형고압블록 형 → 가로×세로 (조달청 「I형」 200×100 · 「U형」 220×110 줄에서)
|
||||
COLORS = ("적색", "녹색", "회색", "갈색", "고동색", "백색", "청색", "황색", "흑색", "컬러")
|
||||
|
||||
|
||||
def _small_block(spec: str, color: str) -> bool:
|
||||
"""소형고압블록 줄인지(잡초방지 뺌) · 색이 맞는지 — 회색은 색 안 적힌 줄도(기본 색)."""
|
||||
if "소형고압블록" not in spec or "잡초방지" in spec:
|
||||
return False
|
||||
if color in spec:
|
||||
return True
|
||||
return color == "회색" and not any(c in spec for c in COLORS)
|
||||
|
||||
|
||||
def find(row: dict, raw: dict, places, pick, PROVINCE=frozenset()):
|
||||
"""(줄, 방식) 또는 (None, 까닭) — `places` · `pick` 은 본 스크립트 것을 받음."""
|
||||
n, spec = row["이름"], row["규격"]
|
||||
base = places
|
||||
|
||||
def places(text): # 「대구경북」 · 「광주전남」 — 붙여 쓴 두 도는 2글자씩 끊어 읽음
|
||||
w = base(text)
|
||||
cut = frozenset(y for x in w for i in range(0, len(x), 2) for y in base(x[i : i + 2]))
|
||||
return cut if w and not w & PROVINCE and cut <= PROVINCE else w
|
||||
|
||||
fam = next(((k, f) for k, pat, f in FAMILIES if re.search(pat, n)), None)
|
||||
if not fam:
|
||||
return None, "이 갈래는 아직 짝짓기 없음"
|
||||
@@ -126,13 +145,32 @@ def find(row: dict, raw: dict, places, pick, PROVINCE=frozenset()):
|
||||
spec, reg = re.sub(r"\([^)]*\)", "", spec), None
|
||||
paren = re.search(r"\(([가-힣]+)", n) if fam[0] in ("교량받침", "합성목재") else None
|
||||
need = pieces(spec)
|
||||
if fam[0] == "주철관": # 이름의 종별(2종 · 3종)도 맞아야
|
||||
keep = lambda r: True # noqa: E731
|
||||
if fam[0] in ("주철관", "벤치플룸"): # 이름의 종별(2종 · 3종)도 맞아야
|
||||
need += [p for p in pieces(n) if re.search(r"\d", p)]
|
||||
if fam[0] == "벤치플룸": # 「140kg」 은 조달청 표기에 없음 · 뚜껑(부품) 줄은 뺌
|
||||
need = [p for p in need if not p.endswith("kg")]
|
||||
keep = lambda r: "(부품)" not in r["prdctSpecNm"] # noqa: E731
|
||||
if fam[0] == "수로관" and (m := re.match(r"M-(\w+)\((\d+)\*(\d+)", spec)):
|
||||
need = [
|
||||
f"m-{m[1].lower()}",
|
||||
f"{m[2]}x{m[3]}",
|
||||
] # 모델 · 가로×세로 — 무개 「M-400C」 는 조달청 모델명과 같음
|
||||
keep = lambda r: re.search(r"(?<![a-z])m-", norm(r["prdctSpecNm"])) is not None # noqa: E731
|
||||
if fam[0] == "블록" and n.startswith("소형고압블록"):
|
||||
m = re.match(r"([IU])형\s*T=(\d+)cm,\s*(\S+)", spec)
|
||||
if not m:
|
||||
return None, "규격 없음"
|
||||
need = [f"{SHAPE[m[1]]}x{int(m[2]) * 10}"]
|
||||
color = m[3].replace("칼라", "컬러")
|
||||
keep = lambda r: _small_block(r["prdctSpecNm"], color) # noqa: E731
|
||||
if not pieces(spec):
|
||||
return None, "규격 없음"
|
||||
hits, seen_reg = [], False
|
||||
for r in rows:
|
||||
s = norm(r["prdctSpecNm"])
|
||||
if not keep(r):
|
||||
continue
|
||||
if not all(has(s, p) if re.search(r"\d", p) else p in s for p in need):
|
||||
continue
|
||||
if paren and norm(paren.group(1)) not in norm(
|
||||
@@ -150,8 +188,9 @@ def find(row: dict, raw: dict, places, pick, PROVINCE=frozenset()):
|
||||
elif want:
|
||||
ok = (
|
||||
want <= have
|
||||
or r["prdctSplyRgnNm"].startswith("전지역")
|
||||
and "제주" not in r["prdctSplyRgnNm"]
|
||||
or r["prdctSplyRgnNm"].startswith(
|
||||
"전지역"
|
||||
) # 「전지역(제주,도서 제외)」 — 제주는 뺀다는 뜻
|
||||
)
|
||||
else:
|
||||
ok = True
|
||||
|
||||
Reference in New Issue
Block a user