- 행정구역 표 = 시도 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>
152 lines
8.1 KiB
Python
152 lines
8.1 KiB
Python
"""재료 자재품목 열 한 벌 · 시도 · 시군구 · 상하차(요구 828 · 830 · 831 · 834 · 836 · 837 · 839 · 842 · PLAN 830-11).
|
|
build_재료_자재품목 이 부름 · 시험 = test_830_2_material_merge.
|
|
|
|
줄 목록 = EST 그대로(지역 · 관급 · 사급 · 제조사 · 조합 줄마다 따로) — 합치지 않음.
|
|
열 한 벌 = COLUMNS — 시도 · 시군구 · 상하차 = 단위 바로 뒤 · 가격 열 = 조달청(관급) · 물가지 넷 · 일반가격(사급).
|
|
지역 글 = 이름 · 규격에 적힌 지역 글(`split_region` 꼴 일곱) — 그 글은 이름 · 규격에서 빼고
|
|
`master_region.split`(표 `_행정구역.json`)으로 시도 · 시군구 두 칸(없음 = - · -) · 조합 이름(「…(협)」)은 비고로
|
|
· 관급 · 사급 글은 이름에 남김 · 상표 · 산지 · 수종 글(「보령웅천석」 · 「농산개발(홍성)」)은 지역 아님
|
|
· EST 이름 칸 30 자에서 잘린 지역 글은 읽히는 데까지(끝 낱말은 하나뿐인 시군구로) · 비고에 CUT.
|
|
사급 / 관급 고르기는 재료 표 열이 아님 — 단가산출 쪽 몫(요구 837).
|
|
상하차 칸 = 상차 · 하차 · -(기본 · 모름) — 이름 · 규격 끝 인도 조건 글로 채우고 그 글은 이름 · 규격에서 뺌(요구 836)
|
|
「도착도」 · 「하차도」 = 하차 · 「채취장상차(도)」 · 「하치장 · 생산공장 상차도」 · 「(상차도)」 = 상차 ·
|
|
「(상차포함)」 = 상차 · 「(상차제외)」 = -(포함 안 됨 · 같은 이름의 상차 줄과 상하차 칸으로 갈림)
|
|
조달청 칸 값은 모두 비움(EST 「가격정보」 — 요구 827 · 831) · 일반가격 = 시스템 관리자가 넣는 칸.
|
|
시도 · 시군구 · 상하차 · 일반가격 손 값은 빌더가 이어 받음(「-」 = 손 값 아님 — 판독 값).
|
|
"""
|
|
|
|
import re
|
|
|
|
import master_region as mr
|
|
|
|
MARKET = ("물가자료", "유통물가", "물가정보", "거래가격")
|
|
SIDO, SIGUNGU = "시도", "시군구" # 표지 칸 — 없음 = NONE · 지역 글 = `where`
|
|
NONE = "-"
|
|
LOAD = "상하차" # 표지 칸(값 칸 아님)
|
|
LOADS = ("-", "상차", "하차") # 첫째 = 기본(모름)
|
|
GOVT = "조달청" # 관급 가격 칸 — 가장 낮은 값 고르기에 안 넣음(요구 826 · 830)
|
|
OWN = "일반가격" # 시스템 관리자 손 입력 칸(사급)
|
|
HAND = (SIDO, SIGUNGU, LOAD, OWN) # 빌더가 이어 받는 손 값 칸
|
|
COLUMNS = ( # 열 한 벌 · 차례(요구 830 · 831)
|
|
"키",
|
|
"구분",
|
|
"상세구분",
|
|
"이름",
|
|
"규격",
|
|
"단위",
|
|
SIDO,
|
|
SIGUNGU,
|
|
LOAD,
|
|
GOVT,
|
|
*MARKET,
|
|
OWN,
|
|
"출처",
|
|
"비고",
|
|
"면수",
|
|
)
|
|
NAME_LOAD = re.compile(
|
|
r",\s*(도착도|하차도|채취장상차도|채취장상차)$|\(((?:상|하)차(?:포함|제외))\)$"
|
|
) # 모 래, 서울, 도착도 · 폐기물운반비(상차포함)
|
|
SPEC_LOAD = re.compile(
|
|
r"\s*(?:\((상차도)\)|((?:하치장|생산공장)\s*상차도))$"
|
|
) # HD=10mm 하치장 상차도
|
|
|
|
|
|
def split_load(row: dict) -> tuple[str, str, str, str | None]:
|
|
"""(인도 조건 글을 뺀 이름, 규격, 상하차 값, 뺀 글) — 도착 · 하차 = 하차 · 상차 = 상차 · 글 없음 = -."""
|
|
name, spec = row.get("이름") or "", row.get("규격") or ""
|
|
m = NAME_LOAD.search(name)
|
|
if m:
|
|
name, text = name[: m.start()], m[1] or m[2]
|
|
if text.endswith("제외"):
|
|
return name, spec, LOADS[0], text
|
|
else:
|
|
m = SPEC_LOAD.search(spec)
|
|
if not m:
|
|
return name, spec, LOADS[0], None
|
|
spec, text = spec[: m.start()], m[1] or m[2]
|
|
return name, spec, "하차" if text.startswith(("도착", "하차")) else "상차", text
|
|
|
|
|
|
def place(row: dict) -> dict:
|
|
"""COLUMNS 차례의 줄 — 첫 칸(열쇠 · 키)은 그대로 · 인도 조건 글은 상하차 칸 · 지역 글은 시도 · 시군구 칸으로 ·
|
|
손 값(시도 · 시군구 · 상하차 — 「-」 아님)이 있으면 그 값 · 조달청 = 빔 · 옛 칸(관급 · 지역)은 뺌."""
|
|
name, spec, load, _ = split_load(row)
|
|
name, spec, region = split_region(name, spec)
|
|
if region == NONE and row.get("지역") not in (None, "", NONE):
|
|
region = row["지역"] # 옛 지역 칸(830-10) — 이름에서 이미 뗀 ESTX 밖 줄
|
|
sido, sgg, union = mr.split(region)
|
|
first = next(iter(row))
|
|
note = row.get("비고")
|
|
if (
|
|
region != NONE
|
|
and len(row.get("이름") or "") >= 30
|
|
and region.count("(") > region.count(")")
|
|
):
|
|
note = " · ".join(x for x in (note, CUT) if x)
|
|
note = " · ".join(x for x in (note, union) if x) or None
|
|
fix = {
|
|
"이름": name,
|
|
"규격": spec,
|
|
SIDO: row.get(SIDO) if row.get(SIDO) not in (None, "", NONE) else sido,
|
|
SIGUNGU: row.get(SIGUNGU) if row.get(SIGUNGU) not in (None, "", NONE) else sgg,
|
|
LOAD: row.get(LOAD) if row.get(LOAD) not in (None, "", NONE) else load,
|
|
GOVT: None,
|
|
"비고": note,
|
|
}
|
|
return {first: row[first], **{c: fix[c] if c in fix else row.get(c) for c in COLUMNS[1:]}}
|
|
|
|
|
|
def where(row: dict) -> str:
|
|
"""줄 지역 글 = 시도 + 시군구(「전체」 · - 뺌) — 「경북 칠곡」 · 「전국」 · 없음 = -(찾기 · 맞대기 · 후보 글)."""
|
|
got = [str(row.get(c)) for c in (SIDO, SIGUNGU) if row.get(c) not in (None, "", NONE, mr.WHOLE)]
|
|
return " ".join(got) or NONE
|
|
|
|
|
|
# ── 지역 글(요구 839 · 조사 818 2 절 꼴 일곱) ─────────────────────────────
|
|
CUT = "지역 글 잘림 — EST 이름 칸 30 자"
|
|
PLACES = frozenset(
|
|
"""전국 서울 부산 대구 인천 광주 대전 울산 세종 제주 경기 강원 충북 충남 전북 전남 경북 경남
|
|
청주 전주 포항 창원 춘천 충주 강릉 천안 원주 여수 구미 순천 수원 목포 금산 영천 논산 삼척 홍성
|
|
익산 청도 양양 남원 공주 군산 김천 가평 용인 포천 경주 아산 제천 여주 보령 부여 당진 청원 음성
|
|
서산 영동 진천 예산 장성 고흥 고창 광양 고령 성주 영주 안동 칠곡 상주 문경 합천 화성 경산""".split()
|
|
) # 콤마 · 괄호 꼴에 적힌 지역 낱말(자료에서) — 관급 꼬리(F3)는 낱말 표 없이 꼬리 전부
|
|
_P = "|".join(sorted(PLACES, key=len, reverse=True))
|
|
_F2 = re.compile(rf"^(.*?)\(({_P})\)-사급$") # 레미콘(서울)-사급
|
|
_F4 = re.compile(rf"^(.*?)\((SD\d+),({_P})\)-관급$") # 이형철근(SD400,제주)-관급
|
|
_F3 = re.compile(
|
|
r"^(.*?\(관급\))-(?!거양|SD\d)(.+)$"
|
|
) # 레미콘(관급)-경상북도(칠곡군) · 거양 = 제조사
|
|
_F1 = re.compile(rf"^({_P})\s*(\(관급\))?$") # 이름 콤마 조각 「, 서울」 · 「, 제주 (관급)」
|
|
_F5 = re.compile(
|
|
rf"\((?:([^()·]+)·)?((?:{_P})(?:지역)?)(,관급)?\)"
|
|
) # (서울) · (건조상태·전국) · (충남지역,관급)
|
|
_F6 = re.compile(
|
|
rf"(?<![가-힣])\s?\(((?:{_P})(?:지역)?)\)"
|
|
) # 규격 42C (서울) · 0.7M(제주지역) · 「농산개발(홍성)」 = 업체 · 지역 아님
|
|
|
|
|
|
def split_region(name: str, spec: str) -> tuple[str, str, str]:
|
|
"""(지역 글을 뺀 이름, 규격, 지역 글 | NONE) — 꼴 일곱(F1 이름 끝 콤마 · F2 (지역)-사급 · F3 (관급)-꼬리 ·
|
|
F4 (강종,지역)-관급 · F5 이름 속 괄호 · F6 규격 속 괄호 · F7 규격 전체) · 관급 · 사급 글은 남김."""
|
|
name, spec = name or "", spec or ""
|
|
if m := _F2.match(name):
|
|
return f"{m[1]}-사급", spec, m[2]
|
|
if m := _F4.match(name):
|
|
return f"{m[1]}({m[2]})-관급", spec, m[3]
|
|
if m := _F3.match(name):
|
|
return m[1], spec, m[2]
|
|
parts = name.split(",")
|
|
for i, part in enumerate(parts[1:], 1):
|
|
if m := _F1.match(part.strip()):
|
|
rest = ",".join(parts[:i] + parts[i + 1 :])
|
|
return (rest.rstrip() + m[2] if m[2] else rest), spec, m[1]
|
|
if m := _F5.search(name):
|
|
inner = ",".join(x for x in (m[1], (m[3] or "")[1:]) if x)
|
|
return name[: m.start()] + (f"({inner})" if inner else "") + name[m.end() :], spec, m[2]
|
|
if m := _F6.search(spec):
|
|
return name, spec[: m.start()] + spec[m.end() :], m[1]
|
|
if spec.strip() in PLACES:
|
|
return name, "", spec.strip()
|
|
return name, spec, NONE
|