Files
Aislo/M02_MasterTemplete/M02_Mat_Place.py
T
eomsangdonandClaude Opus 5.5 b9a8311c6a feat(830-11): 재료 지역 열 → 시도 · 시군구 두 칸 · 행정구역 표 따로(_행정구역.json)
- 행정구역 표 = 시도 17(짧은 이름) + 전국 · 시도별 시군구 229(자치 227 + 제주 행정시 2 · 2026-07-01 인천 제물포 · 영종 · 검단 반영) · 옛 이름(강원도 · 전라북도 · 청원 · 마산 · 진해 · 연기 · 인천 남구 · 중구 · 동구 · 경북 군위) · 별칭(영종도 · 용유도 · 세종시 · 제주도)
- 풀이 master_region.split — 시도만 = 시군구 「전체」 · 시군구만 = 시도 찾음 · 읍면 = 시군구(읍면) · 여러 시도 = 쉼표 · 제외 조건 = 끝에 「 제외」 · 조합 이름 = 비고 · 잘린 끝 낱말 = 하나뿐인 시군구(동두 → 동두천 · 금 → 금천)
- 지역 글 242 종 모두 풀림(못 푼 것 0) · 옛 이름 바꾼 줄 648 · 줄 · 키 · 값 그대로(24,027 · 비고만 조합 121 줄)
- 쓰는 곳: 지역 글 = material_merge.where(시도 + 시군구) — M01 찾기(label · 검색어 · 자재지역) · 표 찾기 · 후보 글 · 불러오기(M02_Mat_Place · Hopyo_Match · Hopyo_Material — 가져온 지역 낱말도 짧은 이름으로 · 시군구가 있으면 그것으로 맞댐)
- M01 화면: 시도 = 고르기(목록 밖 값 「대전,충남」 은 끝에) · 시군구 = 글 · 새 줄 - · -
- 로직 1,520 시험 계산 전후 다름 0 · check_master 틀 · 본문 · 로직 · 조합 · 목차 0 · 단위 27 · 배 7(전과 같음)
- 빌더 adopt 가 로직 장 파일 「차례」 를 하나씩 미는 것은 이번 일 밖 — 되돌림(로직 파일 손대지 않음)
- 새 지킴 test_830_11_material_sido · 낡은 시험 고침: test_830_10 · test_830_2(열 · 지역 기대를 두 칸으로 · 830-11 새 겹침 38 묶음 = 청원 → 청주 4 · 조합 비고 34) · test_m02_hopyo_match(강원도(강릉시) → 강원 강릉)
- 틀 문서 _틀.md 11 절 행정구역 표

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-15 09:56:54 +09:00

98 lines
4.4 KiB
Python

"""불러오기 재료 지역 · 상하차 한 벌(PLAN 830-10 · 830-11 · 요구 839 · 842) — 서버 단독 · 읽기만.
- `line(이름, 규격)` = 가져온 줄 글(경쟁사 이름 · 규격 — 지역 · 인도 조건 글이 그대로 옴)을 M01 재료 표 빌더와
같은 규칙(`material_merge.split_load` · `split_region` · `master_region.split`)으로 읽음 →
{이름, 규격, 지역(「경북 칠곡」 꼴 · `material_merge.where`), 상하차} · 글 없음 = - · 못 푸는 지역 글은 그대로.
- 우리 줄 = M01 재료 표 시도 · 시군구 · 상하차 칸(이름 글로 읽지 않음) · 지역 글 = `material_merge.where`.
- `norm(낱말)` = 가져온 글의 지역 낱말 → 짧은 이름(「경상북도」 → 경북 · 「칠곡군」 → 칠곡).
- `rank(우리 줄, 바라는 것, 가운데 값)` = 같은 품목 줄 여럿에서 고르는 차례 —
상하차가 줄 글과 같음(글 없으면 -) › 값 있는 줄(M01 검색어 고르기와 같음) › 지역: 줄 지역 › 전국(지역 모름 - 포함) › 서울 › 경기 ›
그 밖(값이 가운데 값에 가까운 줄) — 조달청 지역 규칙과 같은 차례(요구 830).
"""
from __future__ import annotations
import re
from statistics import median
from typing import Any
from M01_MasterData import M01_MasterData_Store as m01 # noqa: F401 — scripts 경로를 넣음
import material_merge as mmerge # noqa: E402
NONE = mmerge.NONE
NATION = ("전국", "전지역", mmerge.mr.WHOLE, NONE)
PRICES = ("물가자료", "유통물가", "물가정보", "거래가격", "일반가격") # 사급 값 칸(조달청 뺌)
def line(name: Any, spec: Any) -> dict[str, str]:
"""가져온 줄 → {이름, 규격(지역 · 인도 조건 글 뺌), 지역, 상하차}."""
n, s, load, _ = mmerge.split_load({"이름": str(name or ""), "규격": str(spec or "")})
n, s, where = mmerge.split_region(n, s)
try:
sido, sgg, _ = mmerge.mr.split(where)
where = mmerge.where({mmerge.SIDO: sido, mmerge.SIGUNGU: sgg})
except ValueError:
pass # 표에 없는 글 — 그대로
return {"이름": n, "규격": s, "지역": where, "상하차": load}
def norm(word: str) -> str:
"""지역 낱말 → 짧은 이름(시군구가 있으면 그것 · 시도만이면 시도) — 못 풀면 그대로."""
try:
sido, sgg, _ = mmerge.mr.split(word)
except ValueError:
return word
return sido if sgg in (mmerge.mr.WHOLE, NONE) else sgg
def words(where: Any) -> list[str]:
"""지역 글 → 한글 낱말(전국류 뺌) — 「경상북도(칠곡군)」 → 경상북도 · 칠곡군."""
return [w for w in re.findall(r"[가-힣]{2,}", str(where or "")) if w not in NATION]
def hit(where: Any, want: Any) -> bool:
"""우리 지역 글이 줄 지역 글과 맞음 — 같은 글 · 낱말 앞 두 글자가 하나라도 같음(「경북」 · 「경북제일」)."""
if not want or want == NONE:
return False
if str(where) == str(want):
return True
if isinstance(want, str): # 「경북 포항」 → 포항(시군구가 있으면 그것만) · 「경북」 → 경북
mine = words(want.split(" ", 1)[-1])
else:
mine = words(" ".join(norm(w) for w in want))
return any(a[:2] == b[:2] for a in mine for b in words(where))
def value(row: dict) -> Any:
"""사급 값 칸 다섯 가운데 낮은 값(줄에 「값」 이 있으면 그것) — 없음 = None."""
if "값" in row:
return row["값"]
got = [row[c] for c in PRICES if row.get(c) is not None]
return min(got) if got else None
def middle(rows: list[dict]) -> Any:
got = [v for v in (value(r) for r in rows) if v is not None]
return median(got) if got else None
def rank(row: dict, want_where: Any, want_load: str, mid: Any) -> tuple:
"""고르는 차례 열쇠(작을수록 먼저) — (상하차 다름, 값 없음, 지역 차례, 가운데 값과의 거리)."""
where = str(row.get("지역") or mmerge.where(row))
if hit(where, want_where):
tier = 0
elif where in NATION or not words(where):
tier = 1
else:
tier = 2 if where.startswith("서울") else 3 if where.startswith("경기") else 4
v = value(row)
gap = (
0.0
if tier < 4 or mid is None
else float("inf")
if v is None
else abs(float(v) - float(mid))
)
return ((row.get("상하차") or NONE) != (want_load or NONE), v is None, tier, gap)