feat(842-3): M01 기계 자료 옮김 — 조종원 · 조종원직종 · 잡재료율 칸 → 인력 · 잡품 목록(619 대)
- 빌더 build_기계_인력줄_842_3.py(규칙 = master_machine_crew.from_old_row · 다시 돌려도 같음) - 인력 줄 301 대(1 인 282 · 2 인 18 · 7 인 1 · 직종 빈 줄 42 = 빈 키 = 식 입력 직종 고르기 그대로) - 잡품 줄 328 대(잡재료율 > 0 = 주연료 · 전력 줄마다 그 율 · 「73.7+」 글 연료도 줄) · 범위 글 「13∼18」 2 대 = 잡품 없음(값원문 그대로) - 선원류 칸 79 대 · 비활성 표지 79 대 그대로 · 옛 칸 남음 0 - 바탕 덮음 = 식에 그 줄이 있을 때만(손료만 식을 일부러 부른 단가산출 GC000324 → GC000273 크롤러드릴 = 조종원 안 더함) - 식 고름 = 그 줄 인력도 조종원 있음으로 봄 · 로직 검사 · 값 모으기 = 기계 칸을 목록 읽기로(master_formula_check) - 시험 고침: 821_2 · 822_2 미리보기 = 목록 읽은 값으로 · 822_1 노임 키 = 인력 줄 · 822_2 범위 까닭 글 = 「잡재료율 없음」(범위 글은 값원문) - 전후: 655 대 시간당(hourly · brief · 문서 셈) 653 같음 · 다름 2 = 펌프 준설선 EQ000552 · 553(비활성 · 범위 글) 까닭 글만 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""842-3 자료 옮김 — M01 기계 줄 옛 칸(조종원 · 조종원직종 · 잡재료율) → 인력 · 잡품 목록(요구 849 · 867 · 868 · 870).
|
||||
|
||||
규칙 = `master_machine_crew.from_old_row`(값 · 단위 · 출처 — 사본 대조와 같은 규칙):
|
||||
인력 = 조종원 > 0 이면 [{키 'LB:<조종원직종>'(직종 없음 = 빈 키 = 식 입력 직종 고르기), 인 = 조종원}] · 0 = []
|
||||
잡품 = 잡재료율 > 0 이면 밑 재료 줄(주연료 · 전력 가운데 값 > 0)마다 {줄, 율 = 잡재료율} · 0 = []
|
||||
수 아닌 잡재료율(범위 글 「13∼18」) = 잡품 없음(원문 글은 `값원문` 그대로)
|
||||
칸 차례 = 옛 조종원 자리에 인력 · 옛 잡재료율 자리에 잡품 · 선원류 칸(선원 · 준설선선장 · 준설선기관사 · 고급선원) ·
|
||||
맨 위 칸(비활성 표지 등) = 그대로 · 다시 돌려도 같음(새 꼴 줄 = 그대로).
|
||||
실행 = `./venv/Scripts/python.exe resources/master_data/scripts/build_기계_인력줄_842_3.py [--dry]`
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE))
|
||||
|
||||
import master_machine_crew as mcrew # noqa: E402
|
||||
import master_machine_files as mmf # noqa: E402
|
||||
|
||||
FOLDER = HERE.parent
|
||||
SPOT = {"조종원": mcrew.CREW, "잡재료율": mcrew.MISC} # 옛 칸 자리 → 새 칸
|
||||
|
||||
|
||||
def _order(old: dict, new: dict) -> dict:
|
||||
"""새 묶음을 옛 칸 차례로 — 옛 칸 자리에 새 칸 · 나머지 새 칸은 끝."""
|
||||
out: dict = {}
|
||||
for k in old:
|
||||
if k in new:
|
||||
out[k] = new[k]
|
||||
elif SPOT.get(k) in new:
|
||||
out[SPOT[k]] = new[SPOT[k]]
|
||||
return out | {k: v for k, v in new.items() if k not in out}
|
||||
|
||||
|
||||
def move(row: dict) -> dict:
|
||||
new = mcrew.from_old_row(row)
|
||||
if new is row:
|
||||
return row
|
||||
out = dict(row)
|
||||
for part in ("값", "단위", "출처"):
|
||||
if isinstance(row.get(part), dict):
|
||||
out[part] = _order(row[part], new[part])
|
||||
return out
|
||||
|
||||
|
||||
def main(dry: bool = False) -> Counter:
|
||||
data = mmf.read(FOLDER, mmf.NAME)
|
||||
old = {**data, "줄": list(data["줄"])}
|
||||
rows = [move(r) for r in data["줄"]]
|
||||
seen: Counter = Counter()
|
||||
for a, b in zip(data["줄"], rows):
|
||||
va, vb = a.get("값") or {}, b.get("값") or {}
|
||||
seen["대"] += 1
|
||||
seen["옮김"] += a is not b
|
||||
if vb.get(mcrew.CREW):
|
||||
seen["인력 줄 있음"] += 1
|
||||
seen[f"인력 {sum(r['인'] for r in vb[mcrew.CREW])}인"] += 1
|
||||
seen["직종 빈 줄(직종 고르기)"] += any(not r["키"] for r in vb[mcrew.CREW])
|
||||
seen["잡품 줄 있음"] += bool(vb.get(mcrew.MISC))
|
||||
seen["잡품 범위 글(잡품 없음)"] += isinstance(va.get("잡재료율"), str)
|
||||
seen["선원류 칸 그대로"] += any(k in vb for k in ("선원", "준설선선장", "준설선기관사", "고급선원"))
|
||||
seen["비활성 표지 그대로"] += bool(b.get("비활성"))
|
||||
seen["옛 칸 남음"] += any(k in vb for k in mcrew.OLD)
|
||||
if not dry:
|
||||
mmf.write(FOLDER, {**data, "줄": rows}, old)
|
||||
return seen
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for k, v in sorted(main("--dry" in sys.argv).items()):
|
||||
print(f"{k}\t{v}")
|
||||
@@ -324,23 +324,22 @@ def is_machine(doc) -> bool:
|
||||
|
||||
def row_entry(doc: dict, vals) -> dict | None:
|
||||
"""기계 줄 목록(`인력` · `잡품`) 가운데 식 한 줄로 안 풀리는 부분 = 바탕 덮음 · 다 풀림 = None.
|
||||
풀림 = 식이 그 줄을 갖고 · 인력 직종 하나 · 잡품 = 식 밑 재료 줄마다 같은 율(키 · 수량 없음)
|
||||
(식이 `master_machine_crew.view` 로 읽는 조종원 수 · 직종 · 잡재료율 = 같은 금액)."""
|
||||
풀림 = 인력 직종 하나 · 잡품 = 식 밑 재료 줄마다 같은 율(키 · 수량 없음)(식이 `master_machine_crew.view`
|
||||
로 읽는 조종원 수 · 직종 · 잡재료율 = 같은 금액) · 식에 그 줄이 없음 = 안 얹음(손료만 식을 일부러 부른 단가산출)."""
|
||||
if not isinstance(vals, dict) or not is_machine(doc):
|
||||
return None
|
||||
parts, lines = parts_of(doc), doc.get("금액") or []
|
||||
out: dict = {}
|
||||
crew = vals.get(mcrew.CREW)
|
||||
if isinstance(crew, list) and (crew or parts["crew"]):
|
||||
if isinstance(crew, list) and parts["crew"]:
|
||||
jobs = {mcrew.code(r.get("키")) for r in crew if isinstance(r, dict)}
|
||||
if not (parts["crew"] and crew and len(jobs) == 1):
|
||||
if not (crew and len(jobs) == 1):
|
||||
out["조종원"] = crew
|
||||
misc = vals.get(mcrew.MISC)
|
||||
if isinstance(misc, list) and misc:
|
||||
if isinstance(misc, list) and misc and parts["misc"] is not None:
|
||||
names = sorted(str(lines[i].get("이름") or "") for i in parts["base"])
|
||||
plain = (
|
||||
parts["misc"] is not None
|
||||
and all(isinstance(r, dict) and r.get("줄") and not r.get("키") for r in misc)
|
||||
all(isinstance(r, dict) and r.get("줄") and not r.get("키") for r in misc)
|
||||
and all(r.get("수량") is None for r in misc)
|
||||
and len({_canon(r.get("율")) for r in misc}) == 1
|
||||
and sorted(str(r["줄"]) for r in misc) == names
|
||||
|
||||
@@ -11,6 +11,7 @@ from decimal import Decimal
|
||||
|
||||
import master_formula_sum as ms
|
||||
import master_keys as mk
|
||||
import master_machine_crew as mcrew
|
||||
from master_formula import (
|
||||
_HOLE,
|
||||
BUNDLE,
|
||||
@@ -27,6 +28,12 @@ from master_material import REGION, TARIFF, check_pick
|
||||
|
||||
|
||||
# ── 검사 ──────────────────────────────────────────────────────────────
|
||||
def _vals(row: dict):
|
||||
"""줄 값 — 기계 = 인력 · 잡품 목록을 식이 읽는 칸으로(842-3 `master_machine_crew.view`)."""
|
||||
value = row.get("값")
|
||||
return mcrew.view(value) if str(row.get("키"))[:2] == "EQ" else value
|
||||
|
||||
|
||||
def _choices(row: dict, name: str, master: Master):
|
||||
"""이름이 가질 수 있는 값 — 고르기 입력, 또는 고르기 입력만 끼운 요소 칸인 중간. 모르면 None."""
|
||||
for spec in row.get("입력", []):
|
||||
@@ -39,7 +46,7 @@ def _choices(row: dict, name: str, master: Master):
|
||||
out = set()
|
||||
for ref in _expand(row, node[1][1], master) or []:
|
||||
try:
|
||||
out.add(str(master.get(ref)["값"][node[2]]))
|
||||
out.add(str(_vals(master.get(ref))[node[2]]))
|
||||
except (FormulaError, KeyError, TypeError):
|
||||
pass
|
||||
return sorted(out)
|
||||
@@ -86,7 +93,7 @@ def _check_node(row, node, known: set, master: Master, where: str, out: list, ca
|
||||
if kind == "field" and part[1][0] == "ref":
|
||||
for ref in _expand(row, part[1][1], master) or []:
|
||||
try:
|
||||
value = master.get(ref).get("값")
|
||||
value = _vals(master.get(ref))
|
||||
except FormulaError:
|
||||
continue
|
||||
if not isinstance(value, dict) or part[2] not in value:
|
||||
|
||||
@@ -20,7 +20,7 @@ from numbers import Number
|
||||
CREW, MISC = "인력", "잡품"
|
||||
LISTS = (CREW, MISC)
|
||||
OLD = ("조종원", "조종원직종", "잡재료율")
|
||||
BASE = ("주연료", "전력") # 잡품 줄 기본 = 이 재료 줄(값 > 0)
|
||||
BASE = ("주연료", "전력") # 잡품 줄 기본 = 이 재료 줄(값 있음 · 0 아님)
|
||||
|
||||
|
||||
def code(key) -> str:
|
||||
@@ -53,7 +53,7 @@ def view(vals):
|
||||
def from_old(vals: dict, base: list[str] | None = None) -> dict:
|
||||
"""옛 꼴 값 → 새 꼴(옛 칸 셋 → 인력 · 잡품) — 옛 칸 없음 = 그대로.
|
||||
인력 = 조종원 > 0 이면 한 줄(직종 없음 = 빈 키) · 0 = 빈 목록 · 없음 = 칸 없음.
|
||||
잡품 = 잡재료율 > 0 이면 밑 재료 줄(`base` · 없으면 주연료 · 전력 가운데 값 > 0)마다 그 율 · 0 = 빈 목록."""
|
||||
잡품 = 잡재료율 > 0 이면 밑 재료 줄(`base` · 없으면 주연료 · 전력 가운데 값 있는 줄)마다 그 율 · 0 = 빈 목록."""
|
||||
if not isinstance(vals, dict) or is_new(vals):
|
||||
return vals
|
||||
out = {k: v for k, v in vals.items() if k not in OLD}
|
||||
@@ -62,7 +62,7 @@ def from_old(vals: dict, base: list[str] | None = None) -> dict:
|
||||
out[CREW] = [{"키": f"LB:{job}" if job else "", "인": n}] if n > 0 else []
|
||||
if _isnum(rate):
|
||||
if base is None:
|
||||
base = [b for b in BASE if _pos(vals.get(b))]
|
||||
base = [b for b in BASE if vals.get(b) not in (None, "", 0)] # 「73.7+」 글도 줄
|
||||
out[MISC] = [{"줄": b, "율": rate} for b in base] if rate > 0 and base else []
|
||||
return out
|
||||
|
||||
@@ -71,13 +71,6 @@ def _isnum(v) -> bool:
|
||||
return isinstance(v, Number) and not isinstance(v, bool)
|
||||
|
||||
|
||||
def _pos(v) -> bool:
|
||||
try:
|
||||
return float(v) > 0
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
def summary(vals) -> str:
|
||||
"""목록 요약 글(M01 기계 목록 열 · 비고) — 「1048 1인 · 잡품 주연료 22%」."""
|
||||
if not isinstance(vals, dict):
|
||||
|
||||
Reference in New Issue
Block a user