- 빌더 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>
140 lines
6.8 KiB
Python
140 lines
6.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""기계 한 대 인력 줄 · 잡품 줄 (PLAN 842-3 · 요구 849 · 853 · 867 · 868 · 870) — 기계 줄 값 칸 꼴 · 읽는 법.
|
|
|
|
꼴(기계 줄 `값` — M01 `기계/EQ*.json` · 내 기계 `내기계/<키>.json` · 단가산출 사본 EQ 줄):
|
|
인력 = [{키: 'LB:<인력 원문번호>'(빈 글 = 직종 고르기 — 식 입력 직종), 인: 수, 이름?}] 단위 「인/일」
|
|
잡품 = [{줄: '<식 재료 줄 이름 — 주연료 · 전력>' | 키: '<재료 요소 키>', 이름?, 수량?, 율: %}] 단위 「%」
|
|
줄마다 금액 = 인력: 노임 × 인 × 시간환산 · 잡품: (줄 = 그 재료 줄 수량 · 단가 | 키 = 수량(기본 1) × 재료 단가) × 율 ÷ 100
|
|
= 단가산출 `기계덮음` 조종원 · 잡품 줄과 같은 꼴(`master_doc_cover`) · 옛 칸(조종원 · 조종원직종 · 잡재료율) 없음.
|
|
읽기(`view`) = 식이 읽는 조종원 수(인 합) · 직종(첫 직종) · 잡재료율(첫 줄 율)을 목록에서 셈(저장 안 함) ·
|
|
옛 꼴 줄(옛 칸 있음 — 남의 내 기계 · 옛 사본) = 그대로.
|
|
목록이 식 한 줄로 안 풀리면(직종 여럿 · 재료 키 줄 · 수량 · 식에 그 줄 없음) 셈이 목록을 덮음으로 얹음
|
|
(`master_doc_cover.row_entry` — 단가산출 `기계덮음` 은 그 위에 부분별로 덮음).
|
|
파이썬 · TS 짝 — 짝 = `common_util/common_util_machine_crew.ts` · 거울 시험 `resources/tester/test_842_3_machine_crew.py`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from numbers import Number
|
|
|
|
CREW, MISC = "인력", "잡품"
|
|
LISTS = (CREW, MISC)
|
|
OLD = ("조종원", "조종원직종", "잡재료율")
|
|
BASE = ("주연료", "전력") # 잡품 줄 기본 = 이 재료 줄(값 있음 · 0 아님)
|
|
|
|
|
|
def code(key) -> str:
|
|
"""노임 키 → 인력 원문번호(`LB:1048` → 1048 · 빈 글 = 직종 고르기)."""
|
|
t = str(key or "")
|
|
return t[3:] if t.startswith("LB:") else t
|
|
|
|
|
|
def is_new(vals) -> bool:
|
|
"""새 꼴(옛 칸 없음) 기계 값."""
|
|
return isinstance(vals, dict) and not any(k in vals for k in OLD)
|
|
|
|
|
|
def view(vals):
|
|
"""식이 읽는 값 — 새 꼴이면 옛 칸 셋을 목록에서 셈해 더한 새 묶음 · 그 밖 = 그대로."""
|
|
if not is_new(vals):
|
|
return vals
|
|
crew = vals.get(CREW)
|
|
misc = vals.get(MISC)
|
|
jobs = [code(r.get("키")) for r in crew or [] if isinstance(r, dict) and code(r.get("키"))]
|
|
rate = next((r.get("율") for r in misc or [] if isinstance(r, dict) and r.get("줄")), None)
|
|
return {
|
|
**vals,
|
|
"조종원": None if crew is None else sum((r.get("인") or 0) for r in crew),
|
|
"조종원직종": jobs[0] if jobs else None,
|
|
"잡재료율": None if misc is None else (rate if rate is not None else 0),
|
|
}
|
|
|
|
|
|
def from_old(vals: dict, base: list[str] | None = None) -> dict:
|
|
"""옛 꼴 값 → 새 꼴(옛 칸 셋 → 인력 · 잡품) — 옛 칸 없음 = 그대로.
|
|
인력 = 조종원 > 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}
|
|
n, job, rate = vals.get("조종원"), vals.get("조종원직종"), vals.get("잡재료율")
|
|
if _isnum(n):
|
|
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 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
|
|
|
|
|
|
def _isnum(v) -> bool:
|
|
return isinstance(v, Number) and not isinstance(v, bool)
|
|
|
|
|
|
def summary(vals) -> str:
|
|
"""목록 요약 글(M01 기계 목록 열 · 비고) — 「1048 1인 · 잡품 주연료 22%」."""
|
|
if not isinstance(vals, dict):
|
|
return ""
|
|
crew = [f"{code(r.get('키')) or '직종 고르기'} {r.get('인')}인" for r in vals.get(CREW) or []]
|
|
misc = [
|
|
f"{r.get('이름') or r.get('줄') or r.get('키')} {r.get('율')}%"
|
|
for r in vals.get(MISC) or []
|
|
]
|
|
return " · ".join([*crew, *(["잡품 " + ", ".join(misc)] if misc else [])])
|
|
|
|
|
|
def from_old_row(row: dict) -> dict:
|
|
"""옛 꼴 기계 줄 → 새 꼴(값 · 단위 · 출처) — M01 자료 옮김(842-3) · 사본 대조가 같은 규칙.
|
|
단위 = 인력 「인/일」 · 잡품 「%」 · 출처 = 인력 ← 조종원 · 조종원직종 · 잡품 ← 잡재료율."""
|
|
vals = row.get("값") if isinstance(row, dict) else None
|
|
if not isinstance(vals, dict) or is_new(vals):
|
|
return row
|
|
new = from_old(vals)
|
|
out = {**row, "값": new}
|
|
for part in ("단위", "출처"):
|
|
box = row.get(part)
|
|
if not isinstance(box, dict):
|
|
continue
|
|
got = {k: v for k, v in box.items() if k not in OLD}
|
|
if CREW in new:
|
|
src = " · ".join(
|
|
dict.fromkeys(x for x in (box.get("조종원"), box.get("조종원직종")) if x)
|
|
)
|
|
got[CREW] = "인/일" if part == "단위" else src
|
|
if MISC in new:
|
|
got[MISC] = "%" if part == "단위" else (box.get("잡재료율") or "")
|
|
out[part] = got
|
|
return out
|
|
|
|
|
|
def check(vals) -> list[str]:
|
|
"""목록 칸 틀 — 인력 = [{키 'LB…' | 빈 글, 인 ≥ 0}] · 잡품 = [{줄 주연료 · 전력 | 키, 수량? ≥ 0, 율 ≥ 0}] ·
|
|
30 줄까지 · 옛 칸과 같이 = 두 벌(M01 틀 검사 `check_master` · 저장 손질 `M01_MasterData_Machine.fix`)."""
|
|
if not isinstance(vals, dict):
|
|
return []
|
|
out: list[str] = []
|
|
if any(k in vals for k in LISTS) and any(k in vals for k in OLD):
|
|
out.append("인력 · 잡품 목록과 옛 칸(조종원 · 조종원직종 · 잡재료율)이 같이 있음")
|
|
for col, need in ((CREW, "인"), (MISC, "율")):
|
|
rows = vals.get(col)
|
|
if rows is None:
|
|
continue
|
|
if not isinstance(rows, list) or len(rows) > 30:
|
|
out.append(f"{col} — 목록(30 줄까지)")
|
|
continue
|
|
for n, r in enumerate(rows, 1):
|
|
bad = not isinstance(r, dict) or not _isnum(r.get(need)) or r[need] < 0
|
|
if not bad and r.get("수량") is not None:
|
|
bad = not _isnum(r["수량"]) or r["수량"] < 0
|
|
if not bad and col == CREW:
|
|
bad = not isinstance(r.get("키", ""), str) or not (
|
|
r.get("키", "") == "" or str(r["키"]).startswith("LB")
|
|
)
|
|
if not bad and col == MISC:
|
|
bad = bool(r.get("줄")) == bool(r.get("키")) or (
|
|
bool(r.get("줄")) and r["줄"] not in BASE
|
|
)
|
|
if bad:
|
|
out.append(f"{col} {n} 줄 — 꼴 틀림")
|
|
return out
|