Files
Aislo/resources/master_data/scripts/master_machine_crew.py
T
eomsangdonandClaude Opus 5.5 2c6e49f92c fix(842-3): 범위 잡재료율 기계 둘 · 수식 빌더가 저장본 문서를 조용히 빼던 흠
- 원인: 옮김에서 펌프 준설선 EQ000552 · 553 잡재료율 「13∼18」 이 사라짐 → 옛 엔진 멈춤 · 새 계산기 셈(빈 잡품)
  → GC000268 옵션 「다름」 → 저장 밖 → 그 식을 부르는 문서 320 개가 저장본에서 지워짐(1,520 → 1,200)
- 범위 글 = 잡품 율 글 그대로(from_old · TS fromOld · check 허용) · 두 대 다시 옮김 → 까닭 「범위」 옛 칸과 같음
- 빌더 = 저장본 문서가 저장 밖으로 빠지면 멈춤 · 안 씀 · 키와 까닭 알림(lost) · 시험 802_2 에 lost 0
- 시험 822_2 범위 까닭 기대 되돌림 · 842_3 거울에 범위 값 더함
- 확인: 빌더 --check 저장 1,520 · 기본 다름 0 · 옵션 다름 3(채택) · 저장본 · 목록 같음 · 655 대 시간당(옛 자료 대 새 자료) 다름 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-15 19:25:43 +09:00

150 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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 = 빈 목록 ·
원문 범위 글(「13∼18」) = 율 글 그대로(식이 못 셈 · 옛 칸과 같은 까닭)."""
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 base is None:
base = [b for b in BASE if vals.get(b) not in (None, "", 0)] # 「73.7+」 글도 줄
if _isnum(rate):
out[MISC] = [{"줄": b, "율": rate} for b in base] if rate > 0 and base else []
elif _range(rate) and base: # 원문 범위 「13∼18」 = 율 글 그대로(식이 못 셈 · 까닭 「범위」)
out[MISC] = [{"줄": b, "율": rate} for b in base]
return out
def _range(v) -> bool:
"""원문 범위 글(「13∼18」) — M01 자료에만 · 셈은 못 함."""
return isinstance(v, str) and "∼" in v
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)) and r[need] >= 0 or col == MISC and _range(r.get(need))
)
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