112 lines
3.9 KiB
Python
112 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""기준 제비율표 JB_* 43종 읽기(읽기 전용) — 설치 폴더 System\\ST_BSC\\*.JB_1·JB_M·JB_R·JB_RA.
|
||
|
||
근거: 10_System_186개_파일분석장부.md §4(배포파일 ↔ DBF 구조 ↔ 프로젝트 표) · 12번 §9(5행 묶음 · 열 1~5 간접노무 ·
|
||
6~10 기타경비 · 2020-05-27 확장 = 뒤에 15열 덧붙임) · 파일 이름 '2026(상_0413)_제비율.JB_R' = 기준일
|
||
판(edition): JB_1 41×10 · JB_M 25×10 · JB_R 구형 50×20 · JB_R 2020 확장 50×35 · JB_RA 50×20
|
||
프로젝트 짝: JB_1 → RATE.DBF · JB_M → RATE_M.DBF · JB_R → RATE_R.DBF · JB_RA → RATE_RA.DBF
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pathlib
|
||
import re
|
||
|
||
from dbf_rw import DbfTable
|
||
|
||
JB_DIR = pathlib.Path(r"C:\경영정보(주)\System\ST_BSC")
|
||
TABLE_OF = {"JB_1": "RATE.DBF", "JB_M": "RATE_M.DBF", "JB_R": "RATE_R.DBF", "JB_RA": "RATE_RA.DBF"}
|
||
SHAPES = {"JB_1": {(41, 10)}, "JB_M": {(25, 10)}, "JB_R": {(50, 20), (50, 35)}, "JB_RA": {(50, 20)}}
|
||
|
||
|
||
def _num(s: str) -> float:
|
||
try:
|
||
return float(s.strip() or 0)
|
||
except ValueError:
|
||
return 0.0
|
||
|
||
|
||
def matrix_of(b: bytes) -> tuple[list[str], list[list[float]]]:
|
||
t = DbfTable(b)
|
||
return t.order, [[_num(t.get(i, f)) for f in t.order] for i in range(len(t))]
|
||
|
||
|
||
def base_date(name: str) -> str:
|
||
"""'2026(상_0413,표준시장)' → '2026-04-13' · 월일 없으면 연도만."""
|
||
m = re.match(r"^(\d{4})\([^_]*_?(\d{4})?", name)
|
||
if not m:
|
||
return ""
|
||
y, md = m.group(1), m.group(2)
|
||
return f"{y}-{md[:2]}-{md[2:]}" if md else y
|
||
|
||
|
||
def edition(kind: str, rows: int, cols: int) -> str:
|
||
if kind == "JB_R":
|
||
return "2020 확장(35열 · 국가유산 열)" if cols >= 35 else "구형(20열)"
|
||
return "단일판"
|
||
|
||
|
||
def read_jb(path) -> dict:
|
||
p = pathlib.Path(path)
|
||
kind = p.suffix.upper().lstrip(".")
|
||
fields, m = matrix_of(p.read_bytes())
|
||
shape = (len(m), len(fields))
|
||
return {
|
||
"name": p.name,
|
||
"path": str(p),
|
||
"kind": kind,
|
||
"table": TABLE_OF.get(kind, ""),
|
||
"date": base_date(p.stem),
|
||
"actual": "실적" in p.stem or "표준시장" in p.stem,
|
||
"rows": shape[0],
|
||
"cols": shape[1],
|
||
"edition": edition(kind, *shape),
|
||
"shape_ok": shape in SHAPES.get(kind, set()),
|
||
"fields": fields,
|
||
"matrix": m,
|
||
}
|
||
|
||
|
||
def list_jb(root: pathlib.Path | None = None) -> list[dict]:
|
||
"""설치 폴더 JB_* 전부(행렬 제외 요약) — 종류·기준일 차례."""
|
||
d = pathlib.Path(root or JB_DIR)
|
||
if not d.exists():
|
||
return []
|
||
out = []
|
||
for p in sorted(d.glob("*.JB_*")):
|
||
r = read_jb(p)
|
||
r.pop("matrix")
|
||
out.append(r)
|
||
return sorted(out, key=lambda r: (r["kind"], r["date"], r["name"]))
|
||
|
||
|
||
def match_project(files: dict, root: pathlib.Path | None = None) -> dict:
|
||
"""프로젝트 RATE*.DBF 마다 셀이 모두 같은 기준 JB 파일(없으면 가장 적게 다른 것과 다른 셀 수)."""
|
||
d = pathlib.Path(root or JB_DIR)
|
||
cands = [read_jb(p) for p in sorted(d.glob("*.JB_*"))] if d.exists() else []
|
||
out = {}
|
||
for kind, table in TABLE_OF.items():
|
||
if table not in files:
|
||
continue
|
||
_f, pm = matrix_of(files[table])
|
||
best = None
|
||
for c in (x for x in cands if x["kind"] == kind):
|
||
if len(c["matrix"]) != len(pm):
|
||
continue
|
||
diff = sum(
|
||
1
|
||
for a, b in zip(c["matrix"], pm)
|
||
for j in range(max(len(a), len(b)))
|
||
if abs((a[j] if j < len(a) else 0.0) - (b[j] if j < len(b) else 0.0)) > 1e-9
|
||
)
|
||
if best is None or diff < best[0]:
|
||
best = (diff, c["name"], c["date"])
|
||
if best:
|
||
out[table] = {
|
||
"jb": best[1],
|
||
"date": best[2],
|
||
"diff_cells": best[0],
|
||
"exact": best[0] == 0,
|
||
}
|
||
return out
|