# -*- 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